【问题标题】:Why am I able to change the contents of const char *ptr?为什么我可以更改 const char *ptr 的内容?
【发布时间】:2010-07-12 12:52:55
【问题描述】:

我将一个指针 ptr 传递给一个函数,该函数的原型将它作为 const

foo( const char  *str );

这根据我的理解意味着它将无法更改ptr传递的内容。就像foo( const int i ) 的情况一样。如果foo() 试图改变i 的值,编译器会报错。
但在这里我看到它可以轻松更改ptr 的内容。
请看下面的代码

foo( const char  *str )
{
        strcpy( str, "ABC" ) ;
        printf( "%s(): %s\n" , __func__ , str ) ;
}

main()
{
        char ptr[  ] = "Its just to fill the space" ;
        printf( "%s(): %s\n" , __func__ , ptr ) ;
        foo( const ptr ) ;
        printf( "%s(): %s\n" , __func__ , ptr ) ;
        return;
}

在编译时,我只收到警告,没有错误:

warning: passing argument 1 of ‘strcpy’ discards qualifiers from pointer target type

当我运行它时,我得到一个输出而不是Segmentation Fault

main():它只是为了填充空间
foo(): ABC
main(): ABC

现在,我的问题是
1-原型中的const char *str 究竟是什么意思?
这是否意味着函数不能更改str 的内容?如果是这样,那么上面的程序怎么会改变值?
2-如何确保我传递的指针的内容不会改变?

从上述问题中的“指针内容”,我的意思是“指针指向的内存内容”,而不是“指针中包含的地址”。

编辑

大多数回复说这是因为strcpy 和C 隐式类型转换。但是现在我尝试了这个

foo( const char  *str )
{
        str = "Tim" ;
//      strcpy( str, "ABC" ) ;
        printf( "%s(): %s\n" , __func__ , str ) ;
}

这次的输出是,编译器没有警告

main():它只是为了填充空间
foo(): 蒂姆
main(): 只是为了填满空间

显然,str 指向的内存被更改为包含"Tim" 的内存位置,而它在foo() 中。虽然这次我没有使用strcpy()
const 不应该阻止这个吗?还是我的理解有误?

在我看来,即使使用const,我也可以更改内存引用和内存引用的内容。那有什么用呢?

你能给我一个例子,编译器会给我错误,我试图改变一个 const 指针吗?

感谢大家的时间和精力。

【问题讨论】:

  • this q+a 可以帮助你,基本上你也需要理解为什么str="Tim" 可以,而str[0] = something 不会——重复破坏者没有抓住这个(幸运吗?)
  • +1 表示指向其他线程的指针。这很有帮助,但不如所选答案那么多。如果您考虑原始问题的strcpy 部分,我认为这不是重复的。谢谢大家。

标签: c pointers


【解决方案1】:

你的理解是正确的,const char* 是一个合约,意味着你不能通过这个特定的指针来改变内存。

问题在于 C 在类型转换方面非常松懈。 strcpy 接受一个指向 non-const 字符的指针,并且它隐式const char* 转换为char*(编译器会帮助您)。您可以轻松地传递整数而不是指针。结果,您的函数无法更改ptr 指向的内容,但strcpy 可以,因为它看到一个非常量指针。您不会崩溃,因为在您的情况下,指针指向足够大小的实际缓冲区,而不是只读字符串文字。

为避免这种情况,请查看编译器警告,或编译,例如,使用-Wall -Werror(如果您使用 gcc)。

这种行为是特定于 C 的。例如,C++ 不允许这样做,并且需要显式转换(C 样式转换或 const_cast)来去除 const 限定符,正如您合理预期的那样。

回答扩展问题

您正在将字符串文字分配给非常量字符,不幸的是,这在 C 甚至 C++ 中都是合法的!它被隐式转换为char*,即使现在通过此指针写入将导致未定义的行为。这是一个已弃用的功能,目前只有 C++0x 不允许这种情况发生。

话虽如此,为了停止更改指针本身,您必须将其声明为指向 char 的 const 指针 (char *const)。或者,如果你想让它指向的内容和指针本身都不会改变,请使用指向 const char (const char * const) 的 const 指针。

例子:

void foo (
        char *a,
        const char *b,
        char *const c,
        const char *const d)
    {
    char buf[10];
    a = buf; /* OK, changing the pointer */
    *a = 'a'; /* OK, changing contents pointed by pointer */

    b = buf; /* OK, changing the pointer */
    *b = 'b'; /* error, changing contents pointed by pointer */

    c = buf; /* error, changing pointer */
    *c = 'c'; /* OK, changing contents pointed by pointer */

    d = buf; /* error, changing pointer */
    *d = 'd'; /* error, changing contents pointed by pointer */
}

对于所有错误行,GCC 都会给我“错误:只读位置的分配”。

【讨论】:

  • 请查看我的问题的编辑并进一步指导我。谢谢
  • @andrew-dufresne,我添加了一些示例。
  • 请注意,C 中字符串文字的类型是 char 的数组,而不是 const char(如 C++ 中)。
  • @caf,我刚刚了解到这同样适用于 C++0x 之前的 C++。使用 -Wall -Wextra -ansi -pedantic! 将字符串文字分配给非常量字符不会在 C 和 C++ 中给出任何错误或警告
  • 我相信你的话——我当然不声称自己是 C++ 专家!至少在 C 中是这样的原因是字符串文字(以及使用它们的 C 程序)早于 const 的存在。
【解决方案2】:

"const" 确实是编译时的东西,所以除非指针指向一些无效内存,否则不要指望段错误。当使用 const 指针可能会改变它们指向的任何东西时(在这种情况下,将它传递给接受非 const 的 strcpy),将产生一个警告。

【讨论】:

  • const 可以在运行时强制执行。 const 变量可以放在.rodata 或类似部分中,并加载到操作系统标记为只读的内存页面,在这种情况下尝试更改它们肯定会出现段错误。在某些嵌入式架构上,const 数据甚至可能最终存储在 CPU 无法物理写入的 EEPROM 中。
【解决方案3】:

1-原型中的 const char *str 实际上是什么意思? 这是否意味着函数不能改变str的内容?

是的!这意味着我们无法更改 str 所指向的 somethingchararray of chars)的内容。

如果是这样,那么上面的程序怎么会改变这个值呢?

那是因为strcpy()的原型是char * strcpy ( char * destination, const char * source );

在您的代码中有一个从const char*char* 类型的隐式转换,因为strcpy() 要求它的第一个参数是char* 类型。

从技术上讲,您的代码不正确相当危险。如果你在 C++ 中尝试相同的代码,你肯定会得到一个错误。 C++ 不允许这种隐式转换,但 C 允许。

【讨论】:

    【解决方案4】:

    IIRC const 表示参数的值不能改变。在你的情况下,值是指针指向的地址。 strcpy 不会改变指针指向的地址,而是指针指向的内存。

    在此处使用const 可确保内存引用(地址)未更改。但是,引用的内存可能会更改。这就是这里发生的事情。为指针分配新地址会导致错误。

    旁注
    我认为您的 foo 方法不安全。您最好也传递str 的最大长度并执行长度检查,否则您将打开缓冲区溢出。

    编辑
    我从this site获取了以下内容:

    const char *Str 告诉编译器 DATA指针也指向 是常量。这意味着,Str 可以是 在 Func 中更改,但 *Str 不能。 作为指针的副本传递给 Func,对Str 所做的任何更改都不会 被main....看到了。

    【讨论】:

    • 你说“这里的 const 确保内存引用没有改变”。这一次,我没有使用strcpy()。我在foo() 中使用了str="Jurassic park";,它也被打印出来了。虽然引用的内存没有改变,但内存引用确实改变了,因为str 指向foo() 中的“侏罗纪公园”,而它应该一直指向"Its just to fill the space"。请解释一下,我将不胜感激。
    • 当我查看您的编辑时,这正是我所期望的。调用者可以确定调用函数后指针没有改变。从您的示例输出中,指针在调用者的视图中没有改变。
    • 我编辑了我的帖子并添加了来自lix.polytechnique.fr/~liberti/public/computing/prog/c/C/SYNTAX/… 的信息。基本上他们所说的是,当使用 const 时,会传递原始指针的副本。虽然可以在您的函数中更改该指针,但不会更改原始指针 - 这就是您所看到的。
    • -1 你弄错了const char* 意味着指向的数据不会改变,而不是指针本身。然而,strcpy() 不知道你给了它一个 const char*,所以它会继续并覆盖数据(如果可以的话)。
    【解决方案5】:

    const char*char const* 相同,而不是 char* const。所以这意味着指向你不能改变的东西的指针。

    在您的编辑后详细说明。第一种形式禁止更改数据(除非您隐式或显式强制转换),第二种形式禁止更改指针本身。

    您在编辑后的版本中所做的是更改指针。这在这里是合法的。如果您要同时禁止两者,则必须写 char const* const

    【讨论】:

    • 我用char const *ptr试了一下,结果和const char *ptr一样。不像“所以这意味着一个指向你无法改变的东西的指针。”在这两种情况下,我都可以更改值。
    • 您更改了参数的本地副本,它是指向“const”数据的指针。没关系,你不是说指针不能改,其实是可以的。请参阅问题评论中作为链接给出的问题+答案
    猜你喜欢
    • 2010-10-01
    • 2017-03-25
    • 2011-11-29
    • 2019-02-01
    • 2019-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多