C编程:段错误、printf和相关问题

3

像许多年轻程序员一样,我学会了在代码不正常时,在不同的代码点插入大量“here1”,“here2”等打印到控制台的语句以确定程序出错的有用性。这种暴力调试技术在我整个计算机科学学习过程中多次拯救了我。然而,当我开始使用C语言编程时,我遇到了一个有趣的问题。如果我尝试运行

void* test;

printf("hello world");
test[5] = 234;

当没有为testChar分配内存时,会出现段错误。然而,从代码流程来看,逻辑上应该先输出“hello world”,然后才会发生段错误,但根据我的经验,通常情况下先出现段错误,而且根本不会在控制台上输出“hello world”。(我无法测试这个确切的例子,但是在使用Linux上的gcc时,我遇到过这种情况很多次。)我猜想这可能与编译器重新排列某些内容有关,或者printf使用某种异步刷新的缓冲区,因此不会立即输出。这完全是我自己的猜测,因为我真的不知道为什么会发生这种情况。在我使用的任何其他语言中,无论“testChar = ...”行引起了什么问题,“hello world”都将被打印,因此我可以确定问题所在。
我的问题是:当我编写C程序时,为什么会出现这种情况?为什么不先输出“hello world”?其次,是否有比这更好的C编程调试技术,以实现相同的基本功能?换句话说,有没有一种简单/直观的方法来查找代码中存在问题的行?
7个回答

10
你发布的代码是完全合法的,不应该导致段错误 - 没有必要分配任何内存。你的问题一定出在其他地方 - 请发布最小的能够引起问题的代码示例。
编辑:你现在已经编辑了代码,改变了它的完全意义。但是,“hello world”没有显示的原因是输出缓冲区没有被刷新。尝试添加
fflush( stdout );

在 printf 之后。

关于定位问题源头,您有几个选择:

  • 通过使用 __FILE____LINE__ C 宏,在代码中大量添加 printf 语句
  • 学习使用调试器 - 如果您的平台支持核心转储,您可以使用核心映像来查找错误发生的位置。

我在想呢。挠头 虽然我觉得自己快疯了。 - Aiden Bell
抱歉哈哈,我修复了我的代码,现在它是一个真正的问题。我需要任何会导致段错误的东西。 - JoeCool
“哦糟糕,这很糟糕”将被存储在符号区,并且指针存储在testChar中...您可以通过调试器查看它。 - stefanB
虽然我已将此答案标记为已接受,但我也想指出下面stefanB的答案,该答案提供了如何使用特定调试器查找segfaults的重要建议:https://dev59.com/40fSa4cB1Zd3GeqPBfES#975493 - JoeCool

5

printf 将内容写入缓冲区的标准输出(stdout)。有时候程序崩溃前缓冲区未被刷新,这会导致输出不能被正确显示。以下两种方法可以避免此问题:

  1. 使用 fprintf( stderr, "error string" );,因为stderr不是缓冲的。
  2. 在 printf 调用后添加 fflush( stdout ); 的调用。

正如Neil和其他人所说,代码本身没有问题。除非你开始修改 testChar 指向的缓冲区。


为什么我不需要为char指针malloc?从某种意义上说,编译器是否自动在某个地方malloc内存以存储“oh crap this is bad”字符串,并将testChar指向它? - JoeCool
基本上是的,除了它为字符串分配的内存包含在程序本身中且为只读。如果您尝试修改它,您的应用程序将崩溃。 - Graeme Perrow

5

“你是说,有一种简单/直观的方法可以找到有问题的代码行吗?”

使用gdb(或任何其他调试器)。

为了找到程序段错误的位置,您需要使用-g选项编译它(以包含调试符号),然后从 gdb 中运行应用程序,它将在段错误处停止。

您随后可以使用bt命令查看回溯以查看发生段错误的位置。

例如:

> gdb ./x
(gdb) r
Starting program: /proj/cpp/arr/x 
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_PROTECTION_FAILURE at address: 0x00000000
0x000019a9 in willfail () at main.cpp:22
22          *a = 3;
(gdb) bt
#0  0x000019a9 in willfail () at main.cpp:22
#1  0x00001e32 in main () at main.cpp:49
(gdb) 

3
默认情况下,输出会被缓存,而段错误发生在实际写入标准输出之前。请尝试:
fprintf(stderr, "hello, world\n");

(默认情况下,stderr不带缓冲。)

1

这段代码不应该出现段错误。你只是将指向字面字符串的指针赋值给指针变量。如果你使用 strcpy 复制无效指针的内容,情况会有所不同。

消息未显示可能是由于缓冲 I/O。打印一个换行符 \n 或调用 fflush 刷新输出缓冲区。


仅仅添加 \n 并不能刷新缓冲区,你需要手动调用 fflush 来完成。 - Graeme Perrow

0
void* test;

printf("hello world");
test[5] = 234;

很可能“hello world”被系统缓存了,不会立即打印到屏幕上。它被存储等待着任何负责屏幕写入的进程/线程/其他东西有机会处理它。而当它在等待(并可能缓冲其他数据以输出)时,您的函数正在完成。这导致非法访问和段错误。


0

你有两个问题。第一个问题是你的(原始)代码不会段错误。将该字符串常量分配给char指针是完全有效的。但现在先放一边,假装你放了一些会导致段错误的东西。

通常是缓冲区的问题,C运行库中的缓冲区和操作系统本身的缓冲区。您需要刷新它们。

最简单的方法是(在UNIX中,在Linux的fsync不确定,但是除非系统本身崩溃,否则最终肯定会发生):

printf ("DEBUG point 72\n"); fflush (stdout); fsync (fileno (stdout));

我在UNIX中经常这样做,它可以确保C运行时库被刷新到UNIX (fflush),并且UNIX缓冲区被同步到磁盘 (fsync),如果stdout不是终端设备或者你正在为不同的文件句柄执行此操作,则非常有用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接