【问题标题】:SIGSEGV, (seemingly) caused by printfSIGSEGV,(似乎)由 printf 引起
【发布时间】:2010-12-24 12:47:55
【问题描述】:

首先,对于任何交叉发帖,我们深表歉意。希望我不会在这里重复一个问题,但我无法在其他地方找到这个问题(通过 Google 和 Stack Overflow)。

这是错误的要点。如果我在代码中的任何位置调用printfsprintffprintf 来显示浮点数,我会收到SIGSEGV (EXC_BAD_ACCESS) 错误。举个例子吧。

以下抛出错误:

float f = 0.5f;
printf("%f\n",f);

这段代码没有:

float f = 0.5f;
printf("%d\n",f);

我意识到那里有一个隐式转换,但我不关心那个。我只是无法理解为什么打印浮点数与打印整数会引发错误。

注意:部分代码使用malloc创建一些非常大的多维数组。但是,这些打印语句不会以任何方式引用这些数组。这是我如何声明这些数组的示例。

#define X_LEN 20
#define XDOT_LEN 20
#define THETA_LEN 20
#define THETADOT_LEN 20
#define NUM_STATES (X_LEN+1) * (XDOT_LEN+1) * (THETA_LEN+1) * (THETADOT_LEN+1)
#define NUM_ACTS 100

float *states = (float *)malloc(NUM_STATES * sizeof(float));
// as opposed to float states[NUM_STATES] (more memory effecient)


float **q = (float**)malloc(NUM_STATES * sizeof(float*));

for(int i=0; i < NUM_STATES; i++) {
    float *a = (float*)malloc(NUM_ACTS * sizeof(float));
    for(int j=0; j < NUM_ACTS; j++) {
        a[j] = 0.0f;
    }
    q[i] = a;
}

然后上面的printf 语句出现在代码的后面。

我包含malloc 的原因是因为据我了解,SIGSEGV 与格式不正确的malloc 调用有关。所以,如果数组初始化是导致问题的原因,我想知道:

  • 为什么?
  • 如何更改malloc 代码来解决此问题?

我已经包含了 OS X 生成的崩溃日志,以防万一帮助任何人。

工艺:极[5453]
路径:{已编辑}
标识符:极
版本:         ??? (???)
代码类型:X86-64(本机)
父进程:bash [5441]

日期/时间:2009-12-08 11:38:38.358 -0600
操作系统版本:Mac OS X 10.6.2 (10C540)
报告版本:6

自上次报告以来的间隔:130074 秒
自上次报告以来的崩溃次数:68
自上次报告以来的每个应用程序崩溃:63
匿名 UUID:CA20CF15-8C46-4C85-A793-6C69F9F40140

异常类型:EXC_BAD_ACCESS (SIGSEGV)
异常代码:0x0000000100074f3b 处的 KERN_INVALID_ADDRESS
崩溃的线程:0 调度队列:com.apple.main-thread

线程 0 崩溃:调度队列:com.apple.main-thread
0 libSystem.B.dylib 0x00007fff828d489e __Balloc_D2A + 164
1 libSystem.B.dylib 0x00007fff828d49b8 __d2b_D2A + 45
2 libSystem.B.dylib 0x00007fff828e8c74 __dtoa + 320
3 libSystem.B.dylib 0x00007fff828aa960 __vfprintf + 4980
4 libSystem.B.dylib 0x00007fff828ec7db vfprintf_l + 111
5 libSystem.B.dylib 0x00007fff828ec75e fprintf + 196
6 极 0x00000001000028b5 平衡::sarsa() + 187
7 极 0x0000000100002e54 主 + 49
8 极 0x00000001000010a8 开始 + 52

线程 0 因 X86 线程状态(64 位)而崩溃:
  rax:0x00000000000000001 rbx:0x000000010042cca0 rcx:0x000000010042cca8 rdx:0x0000000100074f3b
  rdi:0x000000000000000e rsi:0x00007fff5fbfecbc rbp:0x00007fff5fbfeba0 rsp:0x00007fff5fbfeb90
   r8: 0x00007fff5fbff0b0 r9: 0x0000000000000000 r10: 0x00000000ffffffff r11: 0x000000010083a40b
  r12:0x0000000000000001 r13:0x00007fff5fbfecb8 r14:0x00007fff5fbfecbc r15:0x000000010000363e
  撕裂:0x00007fff828d489e rfl:0x0000000000010202 cr2:0x0000000100074f3b

二进制图像:
       0x100000000 - 0x100003fff +极点??? (???) {已编辑}
    0x7fff5fc00000 - 0x7fff5fc3bdef dyld 132.1 (???) /usr/lib/dyld
    0x7fff81697000 - 0x7fff8169bff7 libmathCommon.A.dylib ??? (???) /usr/lib/system/libmathCommon.A.dylib
    0x7fff8289c000 - 0x7fff82a5aff7 libSystem.B.dylib ??? (???) /usr/lib/libSystem.B.dylib
    0x7fff83c4c000 - 0x7fff83cc9fef libstdc++.6.dylib ??? (???) /usr/lib/libstdc++.6.dylib
    0x7ffffffe00000 - 0x7fffffe01fff libSystem.B.dylib ??? (???) /usr/lib/libSystem.B.dylib

型号:MacBookPro4,1,BootROM MBP41.00C1.B03,2 个处理器,Intel Core 2 Duo,2.4 GHz,2 GB,SMC 1.27f2
显卡:NVIDIA GeForce 8600M GT、GeForce 8600M GT、PCIe、256 MB
内存模块:global_name
AirPort:spairport_wireless_card_type_airport_extreme (0x14E4, 0x8C),博通 BCM43xx 1.0 (5.10.91.19)
蓝牙:2.2.4f3版本,2个服务,1个设备,1个传入串口
网络服务:AirPort、AirPort、en1
串行 ATA 设备:日立 HTS542520K9SA00,186.31 GB
并行 ATA 设备:MATSHITADVD-R UJ-867
USB 设备:内置 iSight、0x05ac (Apple Inc.)、0x8502、0xfd400000
USB 设备:Apple 内部键盘/触控板、0x05ac (Apple Inc.)、0x0230、0x5d200000
USB 设备:IR 接收器、0x05ac(Apple Inc.)、0x8242、0x5d100000
USB 设备:BRCM2046 集线器、0x0a5c(博通公司)、0x4500、0x1a100000
USB 设备:蓝牙 USB 主机控制器,0x05ac(Apple Inc.),0x820f,0x1a110000

谢谢。

【问题讨论】:

  • 至少你应该检查malloc()返回的值是非空的
  • 对 printf 的调用只会导致代码中其他地方出现问题。这是 C 代码,而不是 C++。
  • 提供给 printf() 的内容无关紧要 - 由调用者在调用函数之前适当调整堆栈。这是 C 与 Pascal(被调用函数负责清理堆栈)相比的便利之一,也是调用约定完全不同的原因。
  • 请参阅tenouk.com/Bufferoverflowc/Bufferoverflow3.html,了解有关调用函数如何从堆栈中为被调用函数推送和弹出参数的更多信息。
  • 你真的应该接受下面给出的建议(多次)并获得 Valgrind。当前版本 (3.5.0) 支持 Mac OS X (10.5.x)。

标签: c malloc printf segmentation-fault


【解决方案1】:

您的代码中其他地方存在与printf 语句无关的错误。您在某处踩踏内存,但直到printf 尝试使用__BAlloc_D2A 分配一些内存时问题才显现出来,因为用于跟踪空闲内存块的堆数据结构已损坏,导致崩溃。

要尝试检测您在内存中踩踏的位置,可以使用多种工具。如果你在 Linux 上,我建议使用valgrind,它本质上是在虚拟机中运行你的代码,并在你做任何非法的事情时告诉你,比如越界读/写内存、读取未初始化的变量等。然而,它是在 Mac OS X 中不可用(目前)。

一种选择是使用libgmalloc

% cat gmalloctest.c
#include <stdlib.h>
#include <stdio.h>

main()
{
  unsigned *buffer = (unsigned *)malloc(sizeof(unsigned) * 100);
  unsigned i;

  for (i = 0; i < 200; i++) {
    buffer[i] = i;
  }

  for (i = 0; i < 200; i++) {
     printf ("%d  ", buffer[i]);
  }
}

% cc -g -o gmalloctest gmalloctest.c
% gdb gmalloctest
Reading symbols for shared libraries .. done
(gdb) set env DYLD_INSERT_LIBRARIES /usr/lib/libgmalloc.dylib
(gdb) r
Starting program: gmalloctest
Reading symbols for shared libraries .. done
GuardMalloc: Allocations will be placed on 16 byte boundaries.
GuardMalloc:  - Some buffer overruns may not be noticed.
GuardMalloc:  - Applications using vector instructions (e.g., SSE or Altivec) should work.
GuardMalloc: GuardMalloc version 19

Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_PROTECTION_FAILURE at address: 0xb000d000
0x00001f65 in main () at gmalloctest.c:10
10          buffer[i] = i;
(gdb) print i
$1 = 100
(gdb) where
#0  0x00001f65 in main () at gmalloctest.c:10
(gdb)

另见Enabling the Malloc Debugging Features

【讨论】:

  • 实际上,从 Valgrind 3.5.0 开始,Mac OS X (10.5.x) 支持的。顺便说一句,Valgrind 摇滚
  • 啊,很高兴知道。也许我最终应该从 10.4 升级。是的,Valgrind 确实坚如磐石。
【解决方案2】:

SIGSEGV 发生在您访问未映射到任何内容的虚拟地址或以不允许的方式访问地址时(例如,尝试写入只读区域)。正如您所说,分段错误可能与堆损坏有关。这是因为在内部,大多数malloc 实现将簿记信息与堆上分配的数据交错。如果该簿记信息被破坏,malloc 的行为是未定义的。直到在程序的很久以后,您才可能看到任何错误。

在这种情况下,printf 可能在内部分配了一些内存,从而触发了故障。解决此问题的最佳方法可能是使用 valgrind 运行您的程序,它会在发生任何堆损坏时立即通知您。

【讨论】:

    【解决方案3】:

    您可能遇到指针算术错误或缓冲区溢出,其副作用是破坏printf

    尝试注释掉大部分代码(printf 除外),看看它是否崩溃。如果没有,那么一点一点地取消-\comment 部分,直到你恢复崩溃。你会知道问题出在哪里。

    另外,如果您使用的是 linux 或任何 unix 变体,请考虑使用 valgrind

    编辑:

    我在您的错误报告中看到了这一点:

    0   libSystem.B.dylib               0x00007fff828d489e __Balloc_D2A + 164
    

    这就是实际崩溃的地方,这似乎是一个低级分配例程。我猜你有一个缓冲区溢出,它破坏了空闲列表,使某些未来的分配中断(例如在这个 printf 中)。

    【讨论】:

      【解决方案4】:

      第一个可能性是你只是简单的 malloc() 为你的数组提供足够的内存,printf() 尝试再 malloc() 多一点,但失败了。我认为这不太可能。

      第二种可能性是您的 printf() 并不像您展示的那么简单,而是一些相当复杂的多级指针表达式,并且该表达式在某处变得疯狂。

      【讨论】:

        【解决方案5】:

        某些 printf 实现在处理 "%f"s 时会执行 malloc。如果它正在这样做,那么如果您在某个时刻内存溢出(即写入分配结束之后),那么 printf 可以尝试进行分配并发现堆损坏并引发错误...

        只是一个想法。

        编辑:可能值得看看你的状态数组是如何填充的......另外两个看起来很好,但你可以在任何地方写到结尾......

        【讨论】:

          【解决方案6】:

          printf("%f", parm) 期望参数是双精度。您的 f 是一个浮点数,它被隐式转换为双精度数。

          也许隐式转换搞砸了???

          尝试显式转换

          float f = 0.5f;
          printf("%f\n",(double)f);
          

          甚至

          float f = 0.5f;
          double ff = f;
          printf("%f\n",ff);
          

          【讨论】:

            【解决方案7】:

            只是一个猜测,但你做过#include &lt;stdlib.h&gt; 吗?在范围内没有malloc() 的原型时,编译器假定malloc() 返回int,这显然不是真的。

            如果我是对的(即使不是这样),它揭示了在 C 中不强制转换 malloc() 的返回值的原因。请注意,如果您同时为 C 和 C++ 编写代码,则需要这样做,但对于纯C、不要强制转换malloc()的返回值,让编译器为你做正确的事情。

            所以,而不是:

            T *data = (T *) malloc(sz * sizeof(T));
            

            改为这样做:

            #include <stdlib.h>
            ...
            T *data = malloc(sz * sizeof *data);
            

            这里,T 是任何类型。优点是:

            1. 如果您忘记#include &lt;stdlib.h&gt;,编译器会报错,
            2. 如果你改变data的类型,malloc()的调用就不需要改变了,并且
            3. 我认为它更易于阅读且不易出错。

            通过转换 malloc() 的返回值,您不会让编译器有机会警告您缺少包含 stdlib.h

            【讨论】:

              猜你喜欢
              • 2016-09-15
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-12-12
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多