SIGSEGV,(貌似)由printf引起

4
首先,对于任何重复发布的内容请见谅。希望我没有在这里重复问题,但是我无法在Google和Stack Overflow中找到其他解决方案。
以下是错误的要点。如果我在代码的任何地方调用printf、sprintf或fprintf来显示浮点数,我会收到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生成的崩溃日志,以防对任何人有帮助。
进程:pole [5453] 路径:{REDACTED} 标识符:pole 版本:??? (???) 代码类型:X86-64 (Native) 父进程: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) 异常代码:KERN_INVALID_ADDRESS at 0x0000000100074f3b 崩溃线程:0 Dispatch queue: com.apple.main-thread
线程0崩溃:Dispatch queue: 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 pole 0x00000001000028b5 Balance::sarsa() + 187 7 pole 0x0000000100002e54 main + 49 8 pole 0x00000001000010a8 start + 52
线程0崩溃时的X86线程状态(64位): rax: 0x0000000000000001 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 rip: 0x00007fff828d489e rfl: 0x0000000000010202 cr2: 0x0000000100074f3b
二进制映像: 0x100000000 - 0x100003fff +pole ??? (???) {REDACTED} 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 0x7fffffe00000 - 0x7fffffe01fff libSystem.B.dylib ??? (???) /usr/lib/libSystem.B.dylib
型号:MacBookPro4,1,BootROM MBP41.00C1
感谢您的选择。

至少你应该检查malloc()返回的值是否非空。 - Simon Nickerson
调用printf函数只是导致你代码中其他问题的表现。而且这是C代码,不是C++。 - anon
请参考 http://www.tenouk.com/Bufferoverflowc/Bufferoverflow3.html 了解调用函数如何从堆栈中推送和弹出被调用函数的参数。 - PP.
你真的应该采纳下面(多次)给出的建议并获取Valgrind。当前版本(3.5.0)支持Mac OS X(10.5.x)。 - Dan Moulding
不要为交叉发布道歉;只需不要交叉发布即可! - Lightness Races in Orbit
显示剩余2条评论
7个回答

5
你的代码中有一个与printf语句无关的bug。你在某个地方破坏了内存,但是问题直到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)

另请参见启用Malloc调试功能


1
实际上,从Valgrind 3.5.0开始,Mac OS X(10.5.x)已经得到支持。顺便说一句,Valgrind非常棒。 - Dan Moulding
啊,好的,知道了。也许我应该终于升级到10.4以外的版本了。没错,Valgrind确实很强大。 - Adam Rosenfield

1

你可能有一个指针算术错误或缓冲区溢出,这会导致printf出现故障。

尝试注释掉大部分代码(除了printf),看看是否会崩溃。如果没有,那么逐渐取消注释,直到再次崩溃。然后你就知道问题出在哪里了。

此外,如果你正在使用Linux或任何Unix变体,请考虑使用valgrind

编辑:

我在你的错误报告中看到了这个:

0   libSystem.B.dylib               0x00007fff828d489e __Balloc_D2A + 164

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


1

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

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


0

只是猜测一下,你是否已经包含了#include <stdlib.h>?如果在范围内没有的原型,则编译器会假定malloc()返回int,这显然是错误的。

如果我是对的(即使不是),它也揭示了在C中不需要强制转换返回值的原因。请注意,如果您同时为C和C ++编写代码,则需要进行强制转换,但对于纯C,请勿强制转换的返回值,让编译器为您处理正确的操作。

所以,代替:

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

应该这样做:

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

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

  1. 如果您忘记了 #include <stdlib.h>,编译器会报错,
  2. 如果更改了 data 的类型,则不需要更改 malloc() 调用,
  3. 我认为这样更易于阅读且更少出错。

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


0
可能性一是你只是为数组分配了足够的内存,printf()尝试分配更多内存并失败。我认为这种可能性非常小。
可能性二是你的printf()不像你展示的那样简单,而是一些相当复杂的多级指针表达式,而该表达式在某个地方出现了问题。

0

printf的一些实现在处理“%f”时会进行malloc。如果它确实这样做,那么如果您在某个时刻溢出了内存(即写入了分配的末尾之后),printf可能会尝试进行分配并发现堆已损坏并抛出错误...

只是一个想法。

编辑:值得看看如何填充状态数组...其他两个似乎很好,但您可能会在任何地方写过头...


0

printf("%f", parm) 期望参数为 double 类型。而你的 f 是 float 类型,会被隐式转换为 double 类型。

可能隐式转换出了问题???

尝试显式转换一下吧。

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

或者甚至更好

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

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