C中局部变量指针问题

先提问题,在类似如下的函数中:

char *GetMemory(void)

{

char p[] = "hello world";

printf("%p\n",p);

return p;

}

问题1:

printf("%p\n",p); // 对p的有什么影响?请对比GetMemory0和GetMemory1

问题2:

p[1] = 'a';  //这个的先后顺序不同,为啥结果不一样?请对比GetMemory2和GetMemory3

已知见解:

char* p = "hello world";

是一个指向常量区地址的指针。

char p[] = "hello world";

是局部字符串数组,会把"hello world"拷贝到函数栈中.

printf("%p",p)会修改栈的中数据的释放原则?

代码示例:

char *GetMemory0(void)

{

char p[] = "hello world";

return p;

}

char *GetMemory1(void)

{

char p[] = "hello world";

printf("%p\n",p);

return p;

}

char *GetMemory2(void)

{

char p[] = "hello world";

printf("%p\n",p);

p[1] = 'a';

return p;

}

char *GetMemory3(void)

{

char p[] = "hello world";

p[1] = 'a';

printf("%p\n",p);

return p;

}

char *GetMemory4(void)

{

char *p = "hello world";

return p;

}

char *GetMemory5(void)

{

char *p = "hello world";

printf("%p\n",p);

return p;

}

int main()

{

char *str = NULL;

str = GetMemory0();

printf(str);

printf("\n---0---\n");

str = GetMemory1();

printf(str);

printf("\n---1---\n");

str = GetMemory2();

printf(str);

printf("\n---2---\n");

str = GetMemory3();

printf(str);

printf("\n---3---\n");

str = GetMemory4();

printf(str);

printf("\n---4---\n");

str = GetMemory5();

printf(str);

printf("\n---5---\n");

}

运行结果为:

0(M�

---0---

0x7fff184d2720

hello world

---1---

0x7fff184d2720

hello world

---2---

0x7fff184d2720

hallo world

---3---

hello world

---4---

0x400988

hello world

---5---

编译过程:

g++ --version

g++ (GCC) 4.8.2 20140206 (prerelease)

g++ -Wall -O -g -c test.cpp -o test.o

test.cpp: 在函数‘char* GetMemory0()’中:

test.cpp:4:10: 警告:返回了局部变量的‘p’的地址 [-Wreturn-local-addr]

char p[] = "hello world";

^

test.cpp: 在函数‘char* GetMemory1()’中:

test.cpp:9:10: 警告:返回了局部变量的‘p’的地址 [-Wreturn-local-addr]

char p[] = "hello world";

^

test.cpp: 在函数‘char* GetMemory2()’中:

test.cpp:15:10: 警告:返回了局部变量的‘p’的地址 [-Wreturn-local-addr]

char p[] = "hello world";

^

test.cpp: 在函数‘char* GetMemory3()’中:

test.cpp:22:10: 警告:返回了局部变量的‘p’的地址 [-Wreturn-local-addr]

char p[] = "hello world";

^

test.cpp: 在函数‘char* GetMemory4()’中:

test.cpp:29:15: 警告:不建议使用从字符串常量到‘char*’的转换 [-Wwrite-strings]

char *p = "hello world";

^

test.cpp: 在函数‘char* GetMemory5()’中:

test.cpp:34:15: 警告:不建议使用从字符串常量到‘char*’的转换 [-Wwrite-strings]

char *p = "hello world";

^

g++ -Wall -O -g test.o -o ./test -lpthread -lstdc++

chmod a+x ./test

回答:

先说一下,和@依云 说的问题应该无关……

另外说一点,调换func0和func1的顺序会出现奇怪的现象,我暂时无法解释。

update1

使用下面的printf,就正常了……

int __printf (const char *format, ...)

{

va_list arg;

int done;

va_start (arg, format);

done = vfprintf (stdout, format, arg);

va_end (arg);

return done;

}

现在的问题是我自己编译glibc就没问题,用pacman的就有问题,pacman的又不能调试,gdb又不支持汇编单步……

我来大胆猜测一下应该是因为不定传参奇特的压栈方式导致的吧……总之你换上puts的结果在我看来才是比较符合逻辑的。。

我有一个怀疑,可能是因为某个不知名的原因在第一次调用printf的时候,printf会去调用一个类似puts这样的函数,导致参数被破坏?但是为什么同样的glibc的代码,自己写一遍就没有问题了……

对了,问题2和问题3我这里没办法复现

�����

---0---

0x7fffffffe480

hello world123456789073421

---1---

0x7fffffffe490

hallo world

---2---

0x7fffffffe490

hallo world

---3---

hello world

---4---

0x400a38

hello world

---5---

update2

上了一个调试器,printf第一次调用的时候代码路径确实不一样。

关键是这句

00000000004005b0 jmp qword ptr [rip+0x00200c8a]

00000000004005b6 push 0

00000000004005bb jmp 0x00000000004005a0

第一次qword ptr [rip+0x00200c8a] = [0000000000601240] = 00000000004005b6

第二次qword ptr [rip+0x00200c8a] = [0000000000601240] = 00007f550b166810

其中第一次会jmp到00000000004005b6,也就是下一句,然后经过一些跳转之后会到

qword ptr [rip+0x00200c8c] = [0000000000601238] = 00007f39a3fcf2b0

在这里会压一堆的栈进去(56那么大),然后call 0x00007f39a3fc8ce0

这个函数会返回r11 = 00007f39a3448810,也就是真实的printf地址,之后再调用printf的时候就直接call这个了。

第二次会jmp到00007f550b166810,那个地方的代码才是printf的真正代码。

那么为什么这个call并不会导致hello world被破坏呢?

因为这时候hello world00007fff:3cd28d28|00007fff3cd28d00|...<....|ASCII "hello world"这个位置,栈顶指针在00007fff:3cd28c40|0000000000000000|........|这个位置,之间间隔了232

但是,这里程序想要压一堆的参数,所以sub rsp 0xd8,把栈顶移动了216,但是实际上程序只往rsp+40,rsp+48,rsp+56,rsp+64,rsp+72写入了东西,然后就直接jz 0x00007f39a344886b,计算一些参数之后就call 0x00007f39a343e500去调用vfprintf了,当然不会影响到这时候相对rsp+232开始的hello world

之后因为栈顶已经被拉的超过hello world了,之后再怎么调用也就没什么关系了。

为了验证这一点,你可以把GetMonery1修改成这样:


char * GetMemory1(void)

{

char p[] = "hello world1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890";

printf("%p\n",p);

return p;

}

你会发现,输出的东西并不完整,反而被破坏了。

123hello world

---0---

0x7fffb5d85a30

hello wo���

---1---

-至于glibc为啥这么做……谁知道……-

按照@依云 说的,应该是Linux下用于动态链接库的PLT

回答:

我能说不懂分析就不要玩这种东西吗……

这是你前两个函数在未优化的情况下的代码:

前两个函数的 diff

优化之后差别就更大了。

PS: chmod 命令是多余的。

回答:

这是问问题,还是自问自答~

只看了开始的GetMemory函数:

GetMemory 函数中 return p 是不对的,函数返回,局部变量分配的内存就释放了~

回答:

永远不要弄这种和编译器实现以及优化相关的东西。

以上是 C中局部变量指针问题 的全部内容, 来源链接: utcz.com/p/194146.html

回到顶部