【问题标题】:Cannot modify C string无法修改 C 字符串
【发布时间】:2023-03-09 15:24:01
【问题描述】:

考虑下面的代码。

诠释主要(无效){ char * test = "abcdefghijklmnopqrstuvwxyz"; 测试[5] = 'x'; printf("%s\n", test); 返回 EXIT_SUCCESS; }

在我看来,这应该打印 abcdexghij。但是,它只是终止而不打印任何内容。

诠释主要(无效){ char * test = "abcdefghijklmnopqrstuvwxyz"; printf("%s\n", test); 返回 EXIT_SUCCESS; }

但是,这工作得很好,所以我误解了操作 C 字符串的概念吗?如果它很重要,我正在运行 Mac OS X 10.6,它是我正在编译的 32 位二进制文​​件。

【问题讨论】:

  • 我不想这么说,但这确实应该在某个 C 常见问题解答中……在 Stack Overflow 上已经被问过数十或数百次了。
  • 很抱歉,如果之前有人问过这个问题,但我无法找到答案。我确实首先阅读了函数参考和所有内容,但我真的没有看到我做错了什么。你能给我指出这样一个 C 常见问题吗?
  • @x3ro: 4 年来没有人回答你关于 C 常见问题的问题?? comp.lang.c FAQ 非常棒。第 8 节涵盖字符和字符串,问题 8.5 指向问题 1.32,该问题解决了您的具体问题。
  • 这就像在做int j = 5; 然后试图将5 变成6。您可以将j 的值更改为6,但您不能将5 本身变成6。您不是想在此处更改 test 的值。您正试图将源代码中的字符串转换为其他内容!

标签: c cstring


【解决方案1】:

This answer 不错,但还不够完整。

char * test = "abcdefghijklmnopqrstuvwxyz";

字符串字面量 指的是具有静态存储持续时间的char[N] 类型的匿名数组对象(意味着它存在于程序的整个执行过程中),其中N 是字符串加一表示终止 '\0'。此对象不是const,但任何修改它的尝试都有未定义的行为。 (如果选择,实现可以使字符串文字可写,但大多数现代编译器不这样做。)

上面的声明创建了一个char[27] 类型的匿名对象,并使用该对象的第一个元素的地址来初始化test。因此,像test[5] = 'x' 这样的赋值尝试修改数组,并且具有未定义的行为;通常它会使你的程序崩溃。 (初始化使用地址是因为字面量是数组类型的表达式,在大多数上下文中它被隐式转换为指向数组第一个元素的指针。)

请注意,在 C++ 中,字符串文字实际上是const,上面的声明是非法的。在 C 或 C++ 中,最好将 test 声明为指向 const 的指针 char:

const char *test = "abcdefghijklmnopqrstuvwxyz";

因此,如果您尝试通过 test 修改数组,编译器会警告您。

(由于历史原因,C 字符串文字不是 const。在 1989 年 ANSI C 标准之前,const 关键字不存在。要求在像您这样的声明中使用它会使代码更安全,但它将需要修改现有代码,这是 ANSI 委员会试图避免的。你应该假装字符串文字是const,即使它们不是。如果你碰巧使用 gcc, -Wwrite-strings 选项将导致编译器将字符串文字视为const——这使得 gcc 不符合标准。)

如果你希望能够修改test所指的字符串,你可以这样定义:

char test[] = "abcdefghijklmnopqrstuvwxyz";

编译器查看初始化程序以确定test 需要多大。在这种情况下,test 将是 char[27] 类型。字符串字面量仍然引用匿名的大部分只读数组对象,但它的值复制test。 (用于初始化数组对象的初始化程序中的字符串文字是数组不会“衰减”为指针的上下文之一;其他情况是当它是一元 &sizeof 的操作数时。)没有对匿名数组的进一步引用,编译器可能会将其优化掉。

在这种情况下,test 本身是一个包含您指定的 26 个字符以及 '\0' 终止符的数组。该数组的生命周期取决于声明test 的位置,这可能很重要,也可能无关紧要。例如,如果你这样做:

char *func(void) {
    char test[] = "abcdefghijklmnopqrstuvwxyz";
    return test; /* BAD IDEA */
}

调用者将收到一个指向不再存在的东西的指针。如果需要在定义test的范围外引用数组,可以定义为static,也可以使用malloc进行分配:

char *test = malloc(27);
if (test == NULL) {
    /* error handling */
}
strcpy(test, "abcdefghijklmnopqrstuvwxyz";

所以数组将继续存在,直到您调用free()。非标准的 strdup() 函数可以做到这一点(它由 POSIX 定义,但不是由 ISO C 定义)。

请注意,test 可能是指针或数组,具体取决于您声明它的方式。如果你将test 传递给一个字符串函数,或者传递给任何接受char* 的函数,那没关系,但是像sizeof test 这样的东西会根据test 是指针还是数组而表现得非常不同.

comp.lang.c FAQ 非常棒。第 8 节涵盖字符和字符串,问题 8.5 指向问题 1.32,该问题解决了您的具体问题。第 6 节介绍了数组和指针之间经常令人困惑的关系。

【讨论】:

    【解决方案2】:

    您应该养成将变量类型与初始化程序类型匹配的习惯。在这种情况下:

    const char* test = "abcdefghijklmnopqrstuvwxyz";
    

    这样你会得到一个编译器错误而不是运行时错误。将编译器警告级别提高到最高也可能有助于避免此类陷阱。为什么这不是 C 中的错误可能是历史原因;当语言标准化时,早期的编译器允许它并且不允许它可能破坏了太多现有代码。但是现在操作系统不允许这样做,所以它是学术性的。

    【讨论】:

      【解决方案3】:

      做:

       char * bar = strdup(foo);
       bar[5] = 'x';
      

      strdup 制作可修改的副本。

      是的,你真的应该测试 strdup 没有返回 NULL。

      【讨论】:

      • ...如果你使用 strdup(),最后是 free(bar)!
      【解决方案4】:

      用初始化值定义的字符指针进入只读段。要使它们可修改,您需要在堆上创建它们(例如使用 new/malloc)或将它们定义为数组。

      不可修改:

      char * foo = "abc";
      

      可修改:

      char foo[] = "abc";
      

      【讨论】:

      • foo[0] = 'x' 我的盒子上仍然有段错误
      【解决方案5】:

      字符串字面量不可修改;最好假设他们不是。有关详细信息,请参阅here

      【讨论】:

        猜你喜欢
        • 2018-04-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多