VBGood网站全文搜索 Google

搜索VBGood全站网页(全文搜索)

VB爱好者乐园(VBGood)

 找回密码
 立即注册
搜索
查看: 6985|回复: 20

【讨论】关于返回C语言局部指针变量的有趣&奇怪问题

[复制链接]
 楼主| 发表于 2010-5-25 17:33:06 | 显示全部楼层 |阅读模式
在 VC6 里编译如下代码:


  1. #include <stdio.h>
  2. char *GetString(void)
  3. {
  4.     char p[] = "ABCDE";
  5.     return p;   // 如果用p[]编译器将提出警告
  6. }
  7. void main()
  8. {
  9.     printf("%s\n", GetString());
  10. }
复制代码


编译时出现警告,运行结果输出乱码。
然后把char p[] = "ABCDE"; 改为 char *p = "ABCDE";
编译时不再出现警告,运行结果也正常。这是为什么?
请自己思考一下再看下面的答案(白字):

答案:p[] 指向的是 char 数组,是局部变量,在堆栈区;*p 指向的是字符串常量,在全局数据区。

然而奇怪的是,我在 VC6 里跟踪p[]版本的汇编代码,如下:
13:       printf("%s\n", GetString());
00401098   call        @ILT+5(GetString) (0040100a)
0040109D   push        eax
0040109E   push        offset string "%s\n" (00422024)
004010A3   call        printf (00401130)
先把 printf 的两个参数入栈再调用它。在入栈后调用前我查看了一下eax指向的内存单元,发现还是 ABCDE,按理说输出也应该是 ABCDE,可是为什么是乱码呢?如果是乱码的话,那应该是退出了 GetString 函数后局部数据区(堆栈)马上被这些乱码覆盖才对,可是并没有被乱码覆盖。
然后我又怀疑是不是 VC6 的内存显示窗口没有及时根据内存中的内容来刷新显示,于是又试了一下 OllyDbg,结果依旧。神奇了。。。哪位大侠来指点一下迷津?
发表于 2010-5-25 18:51:06 | 显示全部楼层
VC2008编译器提示局部或临时变量

warning C4172: returning address of local variable or temporary

评分

参与人数 1人气 +1 收起 理由
VBProFan + 1 和 VC6 的显示完全相同,包括错误号

查看全部评分

回复 支持 反对

使用道具 举报

发表于 2010-5-25 19:03:11 | 显示全部楼层
因为char p[] = "ABCDE";在函数体内
所以退出后这个空间不再保留了
进入printf之前空间还在内存中残留,
进入printf后,被printf的push指令压入的数据覆盖了……所以最后显示乱码
locStr.jpg

评分

参与人数 1威望 +12 收起 理由
VBProFan + 12 精品文章

查看全部评分

回复 支持 反对

使用道具 举报

发表于 2010-5-25 19:12:00 | 显示全部楼层
压入的是那个printf变长参数的结束标志 -1

int __cdecl printf (
        const char *format,
        ...
        )
/*
* stdout 'PRINT', 'F'ormatted
*/
{
        va_list arglist;
        int buffing;
        int retval;

        _VALIDATE_RETURN( (format != NULL), EINVAL, -1);
回复 支持 反对

使用道具 举报

 楼主| 发表于 2010-5-25 20:10:08 | 显示全部楼层
试验了一下,确实要进入 printf 内部才会覆盖“ABCDE”所在的内存区。而 *p 版本的“ABCDE”所在的内存区的地址明显比较大,是全局数据区,所以进入后也不被覆盖。而且输出的“乱码”和原地址的数据相符(对比ASCII字符表,类C、空格、上下箭头)。

但还有个遗留问题没想明白,跟踪 printf,发现输出字符到控制台的是这一句:
0040119A   call        _ftbuf (004014e0)
在这句执行前手工设置对应的内存区为“HIJ”,但输出的结果还是那三个“乱码”……
回复 支持 反对

使用道具 举报

 楼主| 发表于 2010-5-25 20:29:39 | 显示全部楼层
压入的是那个printf变长参数的结束标志 -1

int __cdecl printf (
        const char *format,
        ...
        )
/*
* stdout 'PRINT', 'F'ormatted
*/
{
        va_list arglist;
        i ...
msflexgrid 发表于 2010-5-25 19:12


呵呵,原来 printf 也是用结束标志来处理变长参数的。如果有个变长参数的函数可接受任意数据类型,就不知道它怎么判断结束了。
回复 支持 反对

使用道具 举报

发表于 2010-5-25 22:31:49 | 显示全部楼层
泡饭
我5点20告诉你答案,你5点33就发帖了?

评分

参与人数 1人气 +1 收起 理由
VBProFan + 1 你是嫦娥?

查看全部评分

回复 支持 反对

使用道具 举报

 楼主| 发表于 2010-5-26 10:16:20 | 显示全部楼层

成功

本帖最后由 VBProFan 于 2010-5-26 10:43 编辑


注意,修改后在 ret 前的 pop ebp 之前要记得改回“80 FF 12 00”;另外,要在
00401186   call        _output (004015a0)
前修改数据区才行,如果运行了这句 call 再修改,输出的仍然是原来的乱码。
GetString后printf前.JPG
刚进入printf.jpg
push ebp.JPG
手工修改内存堆栈区.jpg
成功.jpg
回复 支持 反对

使用道具 举报

发表于 2010-5-26 11:23:32 | 显示全部楼层
泡饭你这个代码写得很不严谨啊……虽然返回结果是对的,但是指不定什么时候就出莫名其妙的错误了……

相对标准一点的写法是:
#include <stdio.h>
char *GetString(void)
{
    static char p[最好指定一个大小] = "ABCDE";
    return p;   
}
void main()
{
    printf("%s\n", GetString());
}
回复 支持 反对

使用道具 举报

发表于 2010-5-26 11:25:02 | 显示全部楼层
当然最简单的写法是:
char *GetString(void)
{
    return "ABCDE";
}

可能会提示常指针错误……这样的话就改成const char* XXX……
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

文字版|手机版|小黑屋|VBGood  

GMT+8, 2023-3-22 04:27

VB爱好者乐园(VBGood)
快速回复 返回顶部 返回列表