汇编:C++堆栈变量地址不同/错误?

1 c++ x86 assembly segmentation-fault

我不明白为什么获取一个变量的地址是好的,另一个得到我的0xD然后由于在无效地址(0xD in r.thefn(0);)写入值而崩溃.

这是输出,显示两个变量没有相似的地址.这是GDB显示的内容和汇编输出.我的x86程序集不是很好(我从未编写过x86程序集).我不知道它是否显示了足够的信息,但如果我没有,你能告诉我还有什么需要调试吗?为什么一个变量0xBF8BAF1C而另一个是0xD?C++和汇编代码在下面,但在上面的gist链接中更好地格式化.

有一个static_assert强制String是一个POD,这意味着没有非竞争构造函数.它使用C++生成的默认构造函数.它也在堆栈上,这意味着如果new过载则不会影响它.&没有重载,但在调用函数的前两次看起来也是正确的.

什么可能影响r地址?我可以看到变量varme的地址在第二次和第三次被调用时是相同的,但第三次r是神奇地不同.

这使用Visual C++(2012作品)g ++ 4.6.2在Linux(Ubuntu)上使用g ++ 3.7,3.6.3和Clang 3.0进行编译和运行.

sanity check 1 0xbf8bb4cc
sanity check 2 0xbf8bb4cc 0xbf8bb538
sanity check 3 0xbf8bb4cc 0xbf8bb538
this 0xbf8bb538
sanity check 1 0xbf8baf1c
sanity check 2 0xbf8baf1c 0xbf8baf40
sanity check 3 0xbf8baf1c 0xbf8baf40
this 0xbf8baf40
sanity check 1 0xbf8baf1c
sanity check 2 0xbf8baf1c 0xd
sanity check 3 0xbf8baf1c 0xd
this 0xd
Run Code Online (Sandbox Code Playgroud)

这是代码:另外一个注意事项是有一个static_assert字符串,它强制它是一个POD,这意味着没有非默认的构造函数.我检查&过运营商没有超载.

static int aa=0;
aa++;
int varme;

printf("sanity check 1 %p\n", &varme);
String r;
printf("sanity check 2 %p %p\n", &varme, &r);
//auto v=anotherfn(sz);
printf("sanity check 3 %p %p\n", &varme, &r);
//printf("callingfn=%s,%d %p %p\n", sz,aa, v, &r);
r.thefn(0);
return r;

   ¦0x8084101 <callingfn(char const*)+1> mov %esp,%ebp ¦
   ¦0x8084103 <callingfn(char const*)+3> push %esi ¦
   ¦0x8084104 <callingfn(char const*)+4> sub $0x34,%esp ¦
   ¦0x8084107 <callingfn(char const*)+7> mov 0xc(%ebp),%eax ¦
   ¦0x808410a <callingfn(char const*)+10> mov 0x8(%ebp),%ecx ¦
   ¦0x808410d <callingfn(char const*)+13> mov %eax,-0x8(%ebp) ¦
   ¦0x8084110 <callingfn(char const*)+16> mov 0x81bc894,%eax ¦
   ¦0x8084115 <callingfn(char const*)+21> lea 0x1(%eax),%eax ¦
   ¦0x8084118 <callingfn(char const*)+24> mov %eax,0x81bc894 ¦
   ¦0x808411d <callingfn(char const*)+29> lea -0xc(%ebp),%eax ¦
   ¦0x8084120 <callingfn(char const*)+32> mov %esp,%edx ¦
   ¦0x8084122 <callingfn(char const*)+34> mov %eax,0x4(%edx) ¦
   ¦0x8084125 <callingfn(char const*)+37> movl $0x812ee78,(%edx) ¦
   ¦0x808412b <callingfn(char const*)+43> mov %ecx,-0x10(%ebp) ¦
   ¦0x808412e <callingfn(char const*)+46> mov %eax,-0x14(%ebp) ¦
   ¦0x8084131 <callingfn(char const*)+49> call 0x8049a90 <printf@plt> ¦
   ¦0x8084136 <callingfn(char const*)+54> mov %esp,%ecx ¦
   ¦0x8084138 <callingfn(char const*)+56> mov -0x10(%ebp),%edx ¦
   ¦0x808413b <callingfn(char const*)+59> mov %edx,0x8(%ecx) ¦
   ¦0x808413e <callingfn(char const*)+62> mov -0x14(%ebp),%esi ¦
   ¦0x8084141 <callingfn(char const*)+65> mov %esi,0x4(%ecx) ¦
   ¦0x8084144 <callingfn(char const*)+68> movl $0x812ee8b,(%ecx) ¦
   ¦0x808414a <callingfn(char const*)+74> mov %eax,-0x18(%ebp) ¦
   ¦0x808414d <callingfn(char const*)+77> call 0x8049a90 <printf@plt> ¦
   ¦0x8084152 <callingfn(char const*)+82> mov %esp,%ecx ¦
   ¦0x8084154 <callingfn(char const*)+84> mov -0x10(%ebp),%edx ¦
   ¦0x8084157 <callingfn(char const*)+87> mov %edx,0x8(%ecx) ¦
   ¦0x808415a <callingfn(char const*)+90> mov -0x14(%ebp),%esi ¦
   ¦0x808415d <callingfn(char const*)+93> mov %esi,0x4(%ecx) ¦
   ¦0x8084160 <callingfn(char const*)+96> movl $0x812eea1,(%ecx) ¦
   ¦0x8084166 <callingfn(char const*)+102> mov %eax,-0x1c(%ebp) ¦
   ¦0x8084169 <callingfn(char const*)+105> call 0x8049a90 <printf@plt> ¦
                                                                               ¦
   ¦0x8084169 <callingfn(char const*)+105> call 0x8049a90 <printf@plt> ¦
   ¦0x808416e <callingfn(char const*)+110> mov %esp,%ecx ¦
   ¦0x8084170 <callingfn(char const*)+112> mov -0x10(%ebp),%edx ¦
   ¦0x8084173 <callingfn(char const*)+115> mov %edx,(%ecx) ¦
   ¦0x8084175 <callingfn(char const*)+117> movl $0x0,0x4(%ecx) ¦
   ¦0x808417c <callingfn(char const*)+124> mov %eax,-0x20(%ebp) ¦
   ¦0x808417f <callingfn(char const*)+127> call 0x8056d00 <SomeClass<blah>::thefn(blah*)> ¦
  >¦0x8084184 <callingfn(char const*)+132> add $0x34,%esp ¦
   ¦0x8084187 <callingfn(char const*)+135> pop %esi ¦
   ¦0x8084188 <callingfn(char const*)+136> pop %ebp ¦
   ¦0x8084189 <callingfn(char const*)+137> ret $0x4 ¦
   ¦0x808418c nopl 0x0(%eax)
Run Code Online (Sandbox Code Playgroud)

DCo*_*der 6

设置:

String 定义为:

struct String {
    void *p;
    #ifdef __cplusplus
    /* Operators to help with comparing, etc. */
    /* No additional data members */
    void thefn(int arg); /* Return/argument type not relevant */
    #endif
};
Run Code Online (Sandbox Code Playgroud)

并包括断言以验证sizeof(String) == sizeof(void *)和结构的POD-ness.


这个部分最初没有在问题中提到:调用此函数的函数将相同的String对象返回给它的调用者,但它是从外部C代码调用的,调用者期望一个简单void *而不是String.作者的期望是这应该有效,因为返回值的大小和布局是相同的.


问题:

C++编译器在此函数中使用了命名返回值优化(NRVO).功能签名改为

String fn(char const *);
Run Code Online (Sandbox Code Playgroud)

void fn(char const *, String *);
Run Code Online (Sandbox Code Playgroud)

这在反汇编中是可见的,ebp+0xC在写入之前从中读取,并且没有花费在将有意义的结果放入EAX中.这个ret 0x4部分有点奇怪,因为它意味着只有一个参数从堆栈中清除,但显然GCC/Clang选择实现这个,通过让调用者清除附加参数.

据推测,在调用函数中应用了相同的优化.但是C编译器认为没有理由应用这种优化(毕竟,它期望结果是a void*,而不是结构)并期望返回值像任何指针大小的结果一样被传递.

结果是:

  1. C代码只将一个参数传递给C++代码,该代码需要两个,并且堆栈顶部的垃圾被解释为第二个参数.
  2. C代码不会产生有意义的返回值,C代码希望找到一个返回值.

解决方案:

解决问题的第一步显然是确保C代码需要与C++代码相同的返回值,即结构而不是指针.

但是,我认为没有办法控制是否应用了NRVO,所以我怀疑即使使用正确的返回类型,由于结构的小尺寸,代码的两个方面仍然可能不一致地应用此优化.我也不知道是否extern "C"会对它产生任何影响.

(这个答案总结了评论中所说的内容,并有一些猜测来填补空白)