1

我的应用程序在似乎没有任何问题的代码行上挂起,但是我的 IDE 似乎在该行上挂起并出现错误:

gdb/mi (24/03/09 13:36)(退出。收到信号“SIGSEGV”。描述:分段错误。)

这行代码只是调用了一个没有代码的方法。当您有空引用时,不是分段错误吗?如果是这样,空方法如何具有空引用?

这段代码似乎导致了这个问题:

#include <sys/socket.h>

#define BUFFER_SIZE 256

char *buffer;

buffer = (char*)GetSomePointer()->SomeStackMemoryString.c_str();
int writeResult = write(socketFD, buffer, BUFFER_SIZE);

bzero(buffer, BUFFER_SIZE);
int readResult = read(socketFD, buffer, BUFFER_SIZE);

当使用该read(...)方法的行被注释掉时,问题就消失了。

更新:

我已将问题更改为指向实际问题,并删除了所有不相关的代码-我还回答了我自己的问题,以便阅读本文的人具体知道问题是什么,请在说“你是一个白痴!”。

4

7 回答 7

10

首先,通过空指针或引用调用方法严格来说是未定义的行为。但除非调用是虚拟的,否则它可能会成功。

如果引用/指针为空,则虚拟调用虚拟方法(通过指针/引用,而不是从具有 Class::Method() 调用方式的派生类)总是失败,因为虚拟调用需要访问 vtable 并通过 null 访问 vtable指针/引用是不可能的。所以你不能通过引用/指针调用一个空的虚拟方法。

要理解这一点,您需要更多地了解代码的组织方式。对于每个非内联方法,都有一段代码段包含实现该方法的机器代码。

当调用以非虚拟方式完成时(从派生类或通过引用/指针的非虚拟方法),编译器确切地知道要调用哪个方法(无多态性)。所以它只是插入一个对代码的确切部分的调用,并将这个指针作为第一个参数传递给那里。在通过空指针调用的情况下,也将为空,但您不在乎您的方法是否为空。

当调用虚拟完成时(通过引用/指针),编译器不知道要调用哪个方法,它只知道有一个虚拟方法表,并且表的地址存储在对象中。为了找到要调用的方法,必须首先取消引用指针/引用,获取表,从中获取方法的地址,然后才调用该方法。读取表格是在运行时完成的,而不是在编译期间完成的。如果指针/引用为空,此时会出现分段错误。

这也解释了为什么虚拟调用不能内联。编译器在编译期间查看源代码时根本不知道要内联什么代码。

于 2009-03-24T13:49:00.980 回答
3

没有代码,我能做的最好的就是一个疯狂的猜测。但这里有:

您的“长时间运行的代码”正在写入无效的指针。(要么是一个完全随机的指针,要么是越过缓冲区或数组的开始/开始)。这恰好破坏了您的对象的虚函数表——要么是覆盖指向对象的指针,要么是对象的 vptr 成员,要么是覆盖该类的实际全局虚函数表。

一些事情要尝试:

  • 在您的班级中安排一名哨兵成员。例如,一个 int 在您的构造函数中初始化为已知模式(0xdeadbeef 或 0xcafebabe 很常见),并且从未更改。在进行虚函数调用之前,请检查 (assert()) 它是否仍然具有正确的值。
  • 尝试使用内存调试器。在 Linux 上,选项包括 Electric Fence (efence) 或 Valgrind。
  • 在调试器下运行您的程序(gdb 很好)并四处寻找问题所在 - 在段错误发生后进行事后分析,或者在它将发生段错误的位置之前设置一个断点。
于 2009-03-24T14:30:28.447 回答
3

您的代码是伪造的:缓冲区指向一些随机的内存。我不确定为什么带有 bzero 的行没有失败。

正确的代码是:

   char buffer[BUFFER_SIZE];

   bzero(buffer, BUFFER_SIZE);
   int readResult = read(socketFD, buffer, BUFFER_SIZE);

或者您可以使用 calloc(1, BUFFER_SIZE) 来分配一些内存(并清零)。

于 2009-03-24T19:33:10.343 回答
2

我想不出一个空方法本身会导致这样一个问题的任何原因。没有任何其他背景,我的第一个想法是其他地方的问题正在破坏你的记忆,而它恰好在这里以这种方式表现出来。

我们以前遇到过这样的问题,我在这个答案中写了这个问题。同样的问题也有很多其他好的建议,可能会对您有所帮助。

于 2009-03-24T13:52:18.150 回答
2

当您有空引用时,不是分段错误吗?

可能,但不一定。导致段错误的原因在某种程度上是特定于平台的,但这基本上意味着您的程序正在访问不应该访问的内存。您可能想阅读维基百科文章以更好地了解它是什么。

您可能会检查一件事,空方法是否有返回类型?我可能错了,但是如果它返回一个对象,我可以看到如果该方法实际上没有返回一个对象,那么如何在垃圾上调用复制构造函数。这可能会导致各种不稳定的行为。

如果您将其返回类型更改为 void 或返回一个值,您会得到相同的结果吗?

于 2009-03-24T14:14:00.280 回答
1

问题是因为变量正在使用未分配的内存,这会在函数将数据放入buffer时导致内存损坏。read(...)buffer

通常,bzero 实际上会导致分段错误,但是由于将字符串分配给内存位置,因此允许读取函数写入分配的内存(导致泄漏)。

/* this causes *some* memory to be allocated, 
 * tricking bzero(...) to not SIGSEGV */
buffer = (char*)GetSomePointer()->SomeStackMemoryString.c_str();

int writeResult = write(socketFD, buffer, BUFFER_SIZE);

此更改解决了内存泄漏:

#define BUFFER_SIZE 256

// Use memory on the stack, for auto allocation and release.
char buffer[BUFFER_SIZE];

// Don't write to the buffer, just pass in the chars on their own.
string writeString = GetSomePointer()->SomeStackMemoryString;
int writeResult = write(socketFD, writeString.c_str(), writeString.length());

// It's now safe to use the buffer, as stack memory is used.
bzero(buffer, BUFFER_SIZE);
int readResult = read(socketFD, buffer, BUFFER_SIZE);
于 2009-03-24T19:38:15.010 回答
0

你是从基类的构造函数中调用虚方法吗?这可能是问题所在:如果您从 class 的构造函数中调用纯虚方法BaseBase并且它实际上只在 class 中定义Derived,您最终可能会访问尚未设置的 vtable 记录,因为Derived的构造函数那时还没有被执行。

于 2009-03-24T13:56:08.950 回答