所以我有一个小程序
#include <iostream>
using namespace std;
void lol() {
cout << "How did we get here?"<<std::endl;
}
int main()
{
long a, b, z[10];
cin >> a >> b;
z[a] = b;
}
您可以通过在线编译器here
运行它该程序没有任何意义,但它有一个错误或功能 - 我不知道它是什么。
所以,如果你写这样的东西
main 13 2015
你可能什么也得不到,但如果输入两个幻数13
和4196608
,你就会收到错误。此外,程序执行函数void lol()
并打印行How did we get here?
。
我已经运行nm ./main
并找到了我的函数void lol()
,其地址0000000000400900
等于4196608
(数字系统的基数为10)。
这意味着该计划&#34;跳跃&#34;由于某种原因,这个地址并执行函数void lol()
。而且,如果我改变第一个数字,什么都不会发生。
main 10 4196608
,main 11 4196608
,main 12 4196608
,main 14 4196608
,main 15 4196608
- 完全相同,没有错误,但只要我输入数字13
我得到了这个有趣的行为。
有人能解释一下这里发生了什么吗?
答案 0 :(得分:7)
如果a
的输入是一个大于9的数字(或负数),那么您正在错误地访问z[a]
(超出索引,buffer overflow),因为您声明了一个数组{{ 1}}
这是典型的 undefined behavior (UB)。
UB 非常糟糕,请参阅我的this answer或更多背景信息:
解释一些实际未定义行为的唯一方法是深入研究所有特定于实现的细节(编译器,优化,操作系统,机器代码,处理器等......)。你可能会花费数年时间。 (可能在您的情况下call stack上的返回地址已被long z[10]
函数的地址覆盖。
答案 1 :(得分:1)
使用Basile Starynkevitch给我们的信息,我做了一些实验,结果强烈暗示你正在弄乱回复地址。
我创建了一个中间函数main2()
,它将返回main()
,因此我们知道我们在堆栈位置对函数return address的期望。我的代码打印z[a]
中的先前值,并将其与调用者的内存位置进行比较,即main()
函数:
#include <iostream>
#include <string>
using namespace std;
void lol() {
cout << "How did we get here?"<<std::endl;
}
int main(int argc, char** argv);
void main2()
{
long a, b, z[10];
b = reinterpret_cast<long>(&lol);
a = 15; //The offset depends on your machine, I found out 15 by trial and error
std::cout << "z[a] was " << z[a] << std::endl;
std::cout << "main() was " << reinterpret_cast<long>(&main) << std::endl;
z[a] = b;
}
int main(int argc, char** argv) {
main2();
}
输出结果为:
z[a] was 4196677
main() was 4196657
How did we get here?
Segmentation fault (core dumped)
我不知道每个指令编译成x86 64位后的大小,但是main
实现在asssembly中的指令是call
指令:
main:
.LFB1022:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl %edi, -4(%rbp)
movq %rsi, -16(%rbp)
call _Z5main2v
可以解释main
地址中20个字节的偏移量以及我们最初在z[a]
中的偏移量。