执行printf()和分段错误

Vik*_*ram 22 c printf segmentation-fault

#include<stdio.h>

int main()
{
    char *name = "Vikram";
    printf("%s",name);
    name[1]='s';
    printf("%s",name);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

终端上没有打印输出,只是出现分段故障.但是当我在GDB中运行它时,我得到以下 -

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400525 in main () at seg2.c:7
7       name[1]='s';
(gdb) 
Run Code Online (Sandbox Code Playgroud)

这意味着程序在第7行接收SEG故障(显然我不能在常量字符数组上写入).那么为什么不执行第6行的printf()?

Mys*_*ial 44

这是由于流的缓冲stdout.除非您这样做fflush(stdout)或者您打印换行符"\n",否则可以缓冲输出.

在这种情况下,它是在刷新和打印缓冲区之前的segfaulting.

您可以尝试这样做:

printf("%s",name);
fflush(stdout);        //  Flush the stream.
name[1]='s';           //  Segfault here (undefined behavior)
Run Code Online (Sandbox Code Playgroud)

要么:

printf("%s\n",name);   //  Flush the stream with '\n'
name[1]='s';           //  Segfault here (undefined behavior)
Run Code Online (Sandbox Code Playgroud)

  • 请注意,`fflush`确实是正确的方法 - 换行不能保证触发刷新(我之前被这种行为所困扰). (11认同)

pau*_*aul 10

首先,你应该用"\n"(或至少是最后一个)结束你的printfs.但这与段错无关.

当编译器编译代码时,它会将二进制文件分成几个部分.有些是只读的,有些是可写的.写入只读部分可能会导致段错误.字符串文字通常放在只读部分(gcc应该放在".rodata"中).指针名称指向该ro部分.因此你必须使用

const char *name = "Vikram";
Run Code Online (Sandbox Code Playgroud)

在我的回答中,我使用了一些"可能""应该".行为取决于您的操作系统,编译器和编译设置(链接器脚本定义了这些部分).

添加

-Wa,-ahlms=myfile.lst
Run Code Online (Sandbox Code Playgroud)

到gcc的命令行生成一个名为myfile.lst的文件,其中包含生成的汇编程序代码.在顶部你可以看到

    .section .rodata
.LC0:
    .string "Vikram"
Run Code Online (Sandbox Code Playgroud)

这表明该字符串在Vikram中.

使用相同的代码(必须在全局范围内,否则gcc可能将其存储在堆栈中,注意它是一个数组而不是指针)

char name[] = "Vikram";
Run Code Online (Sandbox Code Playgroud)

产生

    .data
    .type name, @object
    .size name, 7
name:
    .string "Vikram"
Run Code Online (Sandbox Code Playgroud)

语法有点不同,但现在看看它是如何在.data部分中进行的,这是读写的.顺便说一句这个例子是有效的.

  • 如果您注意到的话,OP 并不是询问为什么会发生段错误,而是询问为什么字符串没有首先打印出来。 (2认同)
  • 尽管这可能不是问题的确切答案,但有关 .rodata 和 .data 的提示和解释很有帮助。 (2认同)

Per*_*rry 5

你得到分段错误的原因是C字符串文字是根据C标准只读的,你试图在文字数组"Vikram"的第二个元素上写's'.

你没有得到输出的原因是你的程序在它有机会刷新缓冲区之前缓冲输出并崩溃.除了提供像printf(3)这样的友好格式化函数之外,stdio库的目的是通过缓冲内存缓冲区中的数据来减少I/O操作的开销,并且只在必要时刷新输出,并且仅偶尔执行输入而不是不断.在一般情况下,实际输入和输出不会在您调用stdio函数时发生,但仅在输出缓冲区已满(或输入缓冲区为空)时才会发生.

如果已经设置了FILE对象,那么事情会略有不同,因此它会不断刷新(如stderr),但总的来说,这就是要点.

如果您正在调试,最好fprintf到stderr以确保在崩溃之前刷新调试打印输出.