【问题标题】:Modifying C string constants? [duplicate]修改 C 字符串常量? [复制]
【发布时间】:2010-10-03 14:13:58
【问题描述】:

可能重复:
Why do I get a segmentation fault when writing to a string?

我想编写一个函数来反转传递给它的给定字符串。 但是我不能。如果我为 doReverse 函数(见下面的代码)提供一个字符数组,我的代码运行良好。

我不明白为什么这不起作用。 我可以访问doReverse 中的str[0],但我不能使用char 指针更改数组的任何值。有什么想法吗?

void doReverse(char *str) {
    str[0] = 'b';
}

void main(void) {
    char *str = "abc";
    doReverse(str);
    puts(str);
}

更新:

我知道如何通过将字符数组传递给它来编写反向函数:

void reverse1(char p[]) {
    int i, temp, y;

    for (i = 0, y = strlen(p); i < y; ++i, --y) {
        temp = p[y-1];
        p[y-1] = p[i];
        p[i] = temp;
    }
}

但是,我想写另一个版本,将 char 指针作为参数。

【问题讨论】:

  • 也许你的标题应该更具体一点。一般的“这里出了什么问题”问题对任何人都没有帮助。另外我没有看到任何反向功能。只有一个名为“reverse”
  • 为了清楚起见,我修改了标题 - 希望这会阻止反对票。祝你考试顺利!
  • 尝试将 char* str = "abc" 改为 char str[] = "abc";
  • 对标准C/C++ docs.sun.com/source/819-3689/Ch3.Std.html#23706987654322@的解释

标签: c string pointers


【解决方案1】:

由于您正在准备考试,我将充实我的 cmets 以解释实际发生的情况:

char *str = "abc";

str 是存储在堆栈中的指针。它被初始化为指向文字字符串"abc"。该文字字符串将存储在已编译可执行文件的数据部分中,并在加载程序时加载到内存中。那段内存是只读的,所以当你尝试修改 str 指向的数据时,你会遇到访问冲突。

char* str = malloc(sizeof(char) * 4);
strcpy(str, "abc");

这里,str 是与第一个示例相同的堆栈指针。这一次,它被初始化为指向堆上可以读写的 4 个字符的内存块。起初,该内存块未初始化,可以包含任何内容。 strcpy 读取存储“abc”的只读内存块,并将其复制到 str 指向的读写内存块中。请注意,设置 str[3] = '\0' 是多余的,因为 strcpy 已经这样做了。

顺便说一句,如果您在 Visual Studio 中工作,请改用 strcpy_s 以确保在复制的字符串比您预期的长时不会覆盖缓冲区。

char str[] = "abc"; 

这里的str 现在是一个分配在堆栈上的数组。编译器将调整它的大小,使其完全适合用于初始化它的字符串文字(包括 NULL 终止符)。堆栈内存是可读写的,因此您可以随意修改数组中的值。

char str[4] = "abc";

这实际上与以前的版本相同,只是您告诉编译器您比它更清楚数组应该多长。如果您更改字符串而不是数组大小,您可能会遇到麻烦。

【讨论】:

  • 强制转换 malloc() 的结果绝不是一个好主意。在 C 中,它可以在没有强制转换的情况下工作(除非你没有#include ,在这种情况下,好的编译器会告诉你是否省略强制转换),在 C++ 中你不应该使用 malloc() .
  • 对 - 我只是在上面解释 Eduardo 的回答。
  • 使用 strcpy() 本身确实不是一个好主意,但是 strncpy() 在标准 C 时可以防止缓冲区覆盖。
  • 好吧,乔希,但在我看来,你对我的品味不够挑剔。我还以为我会为“strn”函数插入一个插件。
  • 很公平 - 我对 strn 函数的不满是它们并不总是写尾随 NULL
【解决方案2】:

最简单的解决办法是将str的声明改为

char str[] = "abc";

这使得str 成为一个初始化为字符串“abc”的字符数组。当前,您将 str 作为指向字符的指针,已初始化为指向由字符串文字描述的字符串。有一个关键区别:字符串文字是只读的,以便编译器在存储它们的位置上具有最大的灵活性;修改它们是UB。但是 char 数组是可变的,所以可以修改它们。

附言。 main() 返回一个 int

【讨论】:

  • PPS: int main() 不必明确返回任何内容。在 C 中,所有函数都返回一个 int,而 void 将返回 0,因此在 C 中执行 void main() 是合法的。所以我猜正因为如此,他们允许 C++ 中的主要函数省略 return 语句并假设它是0
【解决方案3】:

老天,老天。对于所有建议实际执行交换的方法的人,请仔细阅读问题;没有什么比不得不重申一个已经完美表达的问题更糟糕的了。不管用于实现交换的方法是什么(temp-swap、xor3-swap 等),这个人似乎对函数的基本和相当基本的内在函数非常熟悉。

但是,如前所述,编译器/链接器通常将所有字符串文字放在目标可执行文件的“常量数据段”中,随后在适当的“加载/执行”期间与不可写的 MMU 描述符相关联调用。随后通过此描述符发出的所有 CPU 写入周期都会自动被 MMU 的异常机制捕获,从而导致强制性的“段错误”或特定于平台的等效项。当然,不言而喻,旧的非 MMU 平台不会表现出这种行为。

尽管这有效地为源语言的“常量/文字”习语提供了运行时支持,但一些平台历来促进了显式编译时段覆盖。然而,这种支持水平已经慢慢减少,取而代之的是更严格/健壮的抽象层,从而使许多明显且通常有用的优化变得站不住脚。随着时间和人员流失在急切的“微软”一代之前逐渐老化的“MC/ASM”理念,程序员不再被认为有足够的知识或责任来做出这种决定。代替我作为项目负责人目睹的许多人为的、更不用说创造性的实施,这绝不是一件坏事。

尽管这篇文章正在迅速演变为偏离主题的违规行为,但我觉得从自上而下的相关问题源源不断地涌现出来,这些问题在我们的行业中逐渐流行起来。作为一个初出茅庐的 C 程序员——一种最初设计用于补充低级开发的语言——我的建议是采用自下而上的方法,并通过一些课外汇编语言开发来增加你的学习。由于算法实现很可能构成您作为应用工程师的主要关注点,因此重要的是要记住,当代 CPU 设计在过去 30 年中经历了同质发展。今天的超快英特尔 CPU 只不过是我在地球还年轻的时候编程的 4/8 位双极处理器的超标量 CMOS 改进。

与流行的看法相反,汇编语言编程相对容易学习,并且在尝试协调高级构造与有问题或深奥的行为时绝对必不可少。一旦考虑到无休止的试验、调试、网络搜索和论坛垃圾邮件,毫无疑问,自下而上的方法无疑是阻力最小的路径。

祝你学业顺利。

【讨论】:

    【解决方案4】:

    因为这是家庭作业,所以我会给出建议,但我不会发布完整的解决方案。

    我猜你在 str[0] = 'b' 上遇到了访问冲突? 这是因为“abc”是字符串文字。

    在调用 reverse 之前复制 str 指向的字符串,或者获取 reverse 分配一个缓冲区并将反转的字符串放入其中。

    请记住,您必须解除分配的所有内存。

    【讨论】:

    • 我认为这不足以帮助他。
    • @Pacerier:如果 4 年后他仍然需要帮助,我认为不会! :)
    【解决方案5】:

    据我所知,常量字符串被实现为常量字符数组(或者,用 C 术语来说,const char [length])。因此,您不能修改其字符。

    尝试动态分配字符串。

    char* str = (char*)malloc(sizeof(char) * 4);
    strcpy(str, "abc");
    str[3] = '\0';
    

    当然,不要忘记在程序结束时释放内存。


    编辑:我不会发布任何与反转字符串相关的内容,因为那是你的工作。

    【讨论】:

    • 谢谢,爱德华多。这就是我一直在寻找的,您的解决方案帮助了我。
    • 或者,如果你想避免不需要的 malloc,只需在堆栈上创建一个数组并让编译器复制字符串:char str[] = "abc";
    • 这个代码片段包含很多杂乱无章的东西,即。 malloc 的转换、“sizeof(char)”和 str[3] 的显式设置。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-10
    • 2020-02-26
    • 1970-01-01
    • 2011-11-26
    • 1970-01-01
    相关资源
    最近更新 更多