【问题标题】:C Programming: seg faults, printf, and related quirks [duplicate]C 编程:seg 错误、printf 和相关怪癖 [重复]
【发布时间】:2009-06-10 13:07:50
【问题描述】:

正如许多年轻程序员所做的那样,我学会了在代码的不同点插入大量“here1”、“here2”等打印到控制台语句以找出我的程序何时出错的有用性。在我的 CS 学习过程中,这种蛮力调试技术为我节省了很多很多次。然而,当我开始用 C 编程时,我偶然发现了一个有趣的问题。如果我尝试运行

void* test;

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

当然,我得到一个段错误,因为我没有为 testChar 分配内存。但是,从逻辑上讲,您会认为“hello world”会在段错误发生之前打印,因为这是代码的流程,但根据我的经验,总是首先发生段错误,并且“hello world” " 根本不会打印到控制台。 (我无法测试这个确切的例子,但我在linux机器上使用gcc多次遇到这种情况。)我猜这与编译器重新排列一些东西和/或printf有关使用某种异步刷新的缓冲区,因此不是立即的。这完全是我的猜测,因为我真的不知道为什么会发生。在我使用过的任何其他语言中,无论“testChar =...”行引起什么问题,“hello world”仍然会被打印出来,因此我可以确定问题出在哪里。

我的问题是为什么在我编写 C 语言时会发生这种情况?为什么不先打印hello world?其次,是否有比这更好的 C 编程调试技术来完成相同的基本任务?例如,一种简单/直观的方法来查找有问题的代码行?

编辑:我偶然给出了一个工作示例哈哈。我现在拥有的应该会导致段错误。有趣的是,当我想要一个段错误时,我通常会得到一个,而现在当我真正想要一个时,我会编写合法代码!

【问题讨论】:

    标签: c debugging segmentation-fault printf-debugging


    【解决方案1】:

    您发布的代码完全合法,不会导致段错误 - 无需 malloc 任何内容。您的问题一定出在其他地方 - 请发布导致问题的最小代码示例。

    编辑:您现在已将代码编辑为具有完全不同的含义。尽管如此,没有显示“hello world”的原因是输出缓冲区没有被刷新。尝试添加

    fflush( stdout );
    

    在 printf 之后。

    关于定位问题的根源,您有两个选择:

    • 使用 __FILE____LINE__ C 宏在您的代码中随意添加 printfs
    • 学习使用调试器 - 如果您的平台支持核心转储,您可以使用核心映像查找错误所在。

    【讨论】:

    • 我是这么认为的。 挠头虽然我正在发疯。
    • 对不起,大声笑,我修复了我的代码,所以这是一个真正的问题。我需要任何会导致段错误的东西。
    • “哦,这太糟糕了”将存储在符号区域(?)中,指针存储在 testChar ...您可以使用调试器查看它。
    • 虽然我将此答案标记为已接受,但我还想指出下面 stefanB 的答案,它为如何使用特定调试器查找段错误提供了很好的建议:stackoverflow.com/questions/975448/…
    【解决方案2】:

    printf 写入缓冲的标准输出。有时该缓冲区在程序崩溃之前不会被刷新,因此您永远看不到输出。避免这种情况的两种方法:

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

    正如 Neil 和其他人所说,编写的代码很好。也就是说,直到您开始修改testChar 指向的缓冲区。

    【讨论】:

    • 为什么我不必为 char poitner 进行 malloc?从某种意义上说,编译器是否会自动为“哦,这太糟糕了”字符串分配内存,然后将 testChar 指向它?
    • 基本上是的,只是它为字符串分配的内存包含在程序本身中并且是只读的。如果您尝试修改它,您的应用程序将会崩溃。
    【解决方案3】:

    “例如,一种简单/直观的方法来查找有问题的代码行?”

    使用 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) 
    

    【讨论】:

      【解决方案4】:

      默认情况下输出缓冲,段错误发生在输出实际写入stdout之前。试试:

      fprintf(stderr, "hello, world\n");
      

      (stderr 默认是无缓冲的。)

      【讨论】:

        【解决方案5】:

        此代码不应出现段错误。您只是将指向文字字符串的指针分配给指针变量。如果你是例如,事情会有所不同。使用strcpy 复制带有无效指针的内容。

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

        【讨论】:

        • 仅添加 \n 不会刷新缓冲区。您需要手动调用 fflush 来执行此操作。
        【解决方案6】:

        你有两个问题。首先是您的(原始)代码不会出现段错误。将该字符串常量分配给 char 指针是完全有效的。但是让我们暂时把它放在一边,假装你在那里放了一些段错误的东西。

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

        最简单的方法是(在 UNIX 中,不完全确定 Linux 中的 fsync,但您应该保证这最终会发生,除非系统本身出现故障):

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

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

        【讨论】:

          【解决方案7】:
          void* test;
          
          printf("hello world");
          test[5] = 234;
          

          系统可能正在某处缓冲“hello world”,并且不会立即打印到屏幕上。它存储的等待机会,任何进程/线程/负责屏幕编写的任何东西都可以有机会处理它。在它等待(并可能缓冲其他数据以输出)的同时,您的功能正在完成。它涉及非法访问和段错误。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-03-04
            • 1970-01-01
            • 2013-09-20
            • 1970-01-01
            • 2011-08-22
            • 1970-01-01
            相关资源
            最近更新 更多