Chinaunix

标题: c语言函数形参的地址关系 [打印本页]

作者: cao627    时间: 2015-01-03 10:34
标题: c语言函数形参的地址关系
本帖最后由 cao627 于 2015-01-03 10:40 编辑
  1. include <stdio.h>

  2. void test1(int a,int b, int c)
  3. {
  4.         int * p;
  5.         p = &a;
  6.         printf("%d\n", *p);
  7.         p--;
  8.         printf("%d\n", *p);
  9.         p--;
  10.         printf("%d\n", *p);
  11. }

  12. void test2(const char * s, ...)
  13. {
  14.         int * p;
  15.         p = (int *)&s;
  16.         p++;
  17.         printf("%d\n", *p);
  18.         p++;
  19.         printf("%d\n", *p);
  20. }

  21. int main()
  22. {

  23.         test1(100,200,300);
  24.         test2("hello world",100,200);
  25.         return 0;
  26. }

复制代码
看到一个视频教程,里面用test2实现了:打印第二个参数和第三个参数的值,依据是形参的地址在内存里紧挨着的,但在我的环境里无法实现这一点。

问题:
1.在test2中定义的是指向正整型指针p,对p++,p的步进是4,而传给test2的实参"hello world" 长度大于4个字节,p++后指向的应该还是在"hello.world"  这串字符存储的空间内,怎么能指到参数100的存储空间,视频教程里确实做到了。

2.在我的环境里test1的参数的地址关系是从左往右递减的,即如上代码是用p--实现依次打印第一,第二,第三个参数。而在视频教程中是用p++。这跟编译器不同的关系吗?


作者: Dannysd    时间: 2015-01-03 17:48
  1. #include<stdio.h>

  2. void test1(int a,int b, int c)
  3. {
  4.         int * p;
  5.         p = &a;
  6.         printf("%d\n", *p);
  7.         p++;
  8.         printf("%d\n", *p);
  9.         p++;
  10.         printf("%d\n", *p);
  11. }

  12. void test2(const char * s, ...)
  13. {
  14.         int * p;
  15.         p = (int *)&s;
  16.         p++;
  17.         printf("%d\n", *p);
  18.         p++;
  19.         printf("%d\n", *p);
  20. }

  21. int main()
  22. {
  23.         test1(100,200,300);

  24.         test2("hello world",100,200);

  25.         return 0;
  26. }
复制代码
  1. ./a.out
  2. 100
  3. 200
  4. 300
  5. 100
  6. 200
复制代码

作者: Dannysd    时间: 2015-01-03 17:51
test2反汇编
  1. 080483f9 <test2>:
  2. 80483f9:       55                      push   %ebp
  3. 80483fa:       89 e5                   mov    %esp,%ebp
  4. 80483fc:       83 ec 18                sub    $0x18,%esp
  5. 80483ff:       8d 45 08                lea    0x8(%ebp),%eax
  6. 8048402:       89 45 fc                mov    %eax,0xfffffffc(%ebp)
  7. 8048405:       83 45 fc 04             addl   $0x4,0xfffffffc(%ebp)
  8. 8048409:       8b 45 fc                mov    0xfffffffc(%ebp),%eax
  9. 804840c:       8b 00                   mov    (%eax),%eax
  10. 804840e:       89 44 24 04             mov    %eax,0x4(%esp)
  11. 8048412:       c7 04 24 60 85 04 08    movl   $0x8048560,(%esp)
  12. 8048419:       e8 9a fe ff ff          call   80482b8 <printf@plt>
  13. 804841e:       83 45 fc 04             addl   $0x4,0xfffffffc(%ebp)
  14. 8048422:       8b 45 fc                mov    0xfffffffc(%ebp),%eax
  15. 8048425:       8b 00                   mov    (%eax),%eax
  16. 8048427:       89 44 24 04             mov    %eax,0x4(%esp)
  17. 804842b:       c7 04 24 60 85 04 08    movl   $0x8048560,(%esp)
  18. 8048432:       e8 81 fe ff ff          call   80482b8 <printf@plt>
  19. 8048437:       c9                      leave  
  20. 8048438:       c3                      ret   
复制代码
  1. 8048405:       83 45 fc 04             addl   $0x4,0xfffffffc(%ebp)
复制代码
这句对应的是p++,其实是在栈上向移动到了下一个地址
作者: cao627    时间: 2015-01-03 19:45
对于test1这样的形式,在我的环境中要用指针递减的方式才能依次打印各个参数。
对于test2这样的形式,在我的环境中无论用指针递增还是递减都不能打印字符串长量后的各个参数。

我的环境是
ubuntu 14.04  64位
gcc 4.8.2
作者: cao627    时间: 2015-01-03 19:53
本帖最后由 cao627 于 2015-01-03 19:54 编辑

@Dannysd
请你在test2中打印一下p取到字符串常量首地址的地址,然后再打印一下p++后的地址。看看两地址的差。
作者: hellioncu    时间: 2015-01-03 20:22
研究这个意义不大,不具有可移植性
作者: Dannysd    时间: 2015-01-03 20:57
本帖最后由 Dannysd 于 2015-01-03 20:58 编辑

我是32位环境,gcc version 4.1.2 Centos
可变参数这个没弄懂。。。。
test1
  1. Breakpoint 1, test1 (a=100, b=200, c=300) at c.c:13
  2. 13              p = &a;
  3. (gdb) n
  4. 14              printf("%d\n", *p);
  5. (gdb) p a
  6. $8 = 100
  7. (gdb) p *p
  8. $9 = 100
  9. (gdb) p &a
  10. $10 = (int *) 0xbfffc310
  11. (gdb) p/x p
  12. $11 = 0xbfffc310
  13. (gdb) n
  14. 100
  15. 15              p++;
  16. (gdb) p *p
  17. $12 = 100
  18. (gdb) p/x p
  19. $13 = 0xbfffc310
  20. (gdb) n
  21. 16              printf("%d\n", *p);
  22. (gdb) p *p
  23. $14 = 200
  24. (gdb) p/x p
  25. $15 = 0xbfffc314
  26. (gdb) n
  27. 200
  28. 17              p++;
  29. (gdb) p *p
  30. $16 = 200
  31. (gdb) p/x p
  32. $17 = 0xbfffc314
  33. (gdb) n
  34. 18              printf("%d\n", *p);
  35. (gdb) p *p
  36. $18 = 300
  37. (gdb) p/x p
  38. $19 = 0xbfffc318
  39. (gdb) n
  40. 300
  41. 19      }
复制代码
============================
test2
  1. Breakpoint 1, test2 (s=0x8048564 "hello world") at c.c:24
  2. 24              p = (int *)&s;
  3. (gdb) p *p
  4. $1 = 200
  5. (gdb) p/x p
  6. $2 = 0xbfffe588
  7. (gdb) n
  8. 25              p++;
  9. (gdb) p *p
  10. $3 = 134514020
  11. (gdb) p/x p
  12. $4 = 0xbfffe580
  13. (gdb) n
  14. 26              printf("%d\n", *p);
  15. (gdb) p *p
  16. $5 = 100
  17. (gdb) p/x p
  18. $6 = 0xbfffe584
  19. (gdb) n
  20. 100
  21. 27              p++;
  22. (gdb) p *p
  23. $7 = 100
  24. (gdb) p/x p
  25. $8 = 0xbfffe584
  26. (gdb) n
  27. 28              printf("%d\n", *p);
  28. (gdb) p *p
  29. $9 = 200
  30. (gdb) p/x p
  31. $10 = 0xbfffe588
  32. (gdb) n
  33. 200
  34. 29      }
  35. (gdb) p *p
  36. $11 = 200
  37. (gdb) p/x p
  38. $12 = 0xbfffe588
  39. (gdb) n
  40. main () at c.c:37
  41. 37              return 0;
  42. (gdb) n
  43. 38      }
  44. (gdb) n
  45. 0x00c44ebc in __libc_start_main () from /lib/libc.so.6
  46. (gdb) n
  47. Single stepping until exit from function __libc_start_main,
  48. which has no line number information.

  49. Program exited normally.
复制代码

作者: cao627    时间: 2015-01-03 22:17
本帖最后由 cao627 于 2015-01-03 22:28 编辑
1.在test2中定义的是指向正整型指针p,对p++,p的步进是4,而传给test2的实参"hello world" 长度大于4个字节,p++后指向的应该还是在"hello.world"  这串字符存储的空间内,怎么能指到参数100的存储空间,视频教程里确实做到了。



("hello world",100,200) 在内存栈中开辟的是   存放helllo world字符串的首地址的空间,4字节空间,4字节空间
p首先取得是存放helllo world字符串的首地址的空间的地址。
对p++后就取到了存放helllo world字符串的首地址的空间后面的4字节空间


对于32位系统,地址的长度是4字节,上面的存放helllo world字符串的首地址的空间就是4字节。p是定义的是指向int型的,p++的步进是4, 所以*(p++) 取得存放helllo world字符串的首地址的4字节空间的后面一个4字节的空间的内容。

但是对于64位系统,地址的长度是8字节,上面的存放helllo world字符串的首地址的空间就是8字节。p是定义的是指向int型的,p++的步进是4,所以*(P++)不能取到存放helllo world字符串的首地址的8字节空间后面一个4字节空间的内容。

这可能就是我的系统(64位)上形参地址是从左往又依次递减的缘故?
这样:存放helllo world字符串的首地址的8字节空间的地址高于紧挨它的形参括号中它右边4字节空间的地址,设形参括号中它右边4字节空间的地址是A,由于这块空间的大小为4字节,所以存放helllo world字符串的首地址的8字节空间的地址就是A+4。这样p取到存放helllo world字符串的首地址的8字节空间的地址,*(p--)就正好取到形参括号中它右边4字节空间内容。
作者: Dannysd    时间: 2015-01-03 22:39
回复 8# cao627


    默认的是cdecl,从右至左压参入栈

   要不加上 __attribute__((cdecl))这个再试试?
作者: zhaohongjian000    时间: 2015-01-04 09:45
取决于ABI规范,函数调用约定属于ABI规范的一部分。简单的说,现在还在用内存传参数的,只有32位的x86了。
x86-64、arm这种都有完整的文档,怎么传参数查看文档即可。

x86-64的附个链接:http://www.x86-64.org/documentation/abi.pdf
作者: idi0t    时间: 2015-01-04 17:26
回复 10# zhaohongjian000


    不明白什么叫“用内存传参数”,怎么理解?
作者: zhaohongjian000    时间: 2015-01-04 17:29
回复 11# idi0t


    就是把参数放内存里啊。一般就是放在stack上,从右向左。这么做的原因是为了支持可变参数的函数,因为第一个参数的地址是在顶部。
作者: idi0t    时间: 2015-01-05 09:14
回复 12# zhaohongjian000


    那为什么说“简单的说,现在还在用内存传参数的,只有32位的x86了”,其它的是怎么传的呢,能否简单说下。
作者: zhaohongjian000    时间: 2015-01-05 11:05
回复 13# idi0t

放寄存器里。比如用8个寄存器用于参数传递,从左到右依次放到1到8号寄存器中。超过8个参数再使用内存。返回值放到第一个寄存器中。
当返回值尺寸比较大时(比如返回结构体),有的ABI规定由caller分配内存存放返回值,并把地址作为额外参数传递给callee。

有浮点运算部件的情况下,浮点参数一般是单独放在浮点部件的寄存器中的。

总体方法都大体相似,但具体细节非常多。单单调用约定这一块可能就有几十种特殊情况。你可以找个ABI规范看一下,比如AMD64的。


x86(32位)的情况比较复杂,因为在发展早期没有确定一个统一的ABI规范。各个编译器、系统遵循的ABI都可能不一样。而且由于多个编译器在
x86(32位)上都采用栈来传递参数,让很多不明所以的人误认为参数天生就是要通过栈来传递的。
作者: yulihua49    时间: 2015-01-05 15:35
cao627 发表于 2015-01-03 10:34
看到一个视频教程,里面用test2实现了:打印第二个参数和第三个参数的值,依据是形参的地址在内存里紧挨着的 ...

X64,前6个形参在寄存器,你上哪儿找地址?
学一下ABI。
作者: idi0t    时间: 2015-01-05 15:53
回复 14# zhaohongjian000


    恩,谢谢解释,没接触过64位的,学习了,突然之前无意中看过别人反汇编arm平台下的程序好像也是用寄存器传的。
作者: kaede_1    时间: 2015-01-05 16:39
LZ,有结果了吗?我在我的环境下试了一下你的程序,调试如下:
  1. GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-51.el7
  2. Copyright (C) 2013 Free Software Foundation, Inc.
  3. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
  4. This is free software: you are free to change and redistribute it.
  5. There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
  6. and "show warranty" for details.
  7. This GDB was configured as "x86_64-redhat-linux-gnu".
  8. For bug reporting instructions, please see:
  9. <http://www.gnu.org/software/gdb/bugs/>...
  10. Reading symbols from /root/Project/programing/C/a.out...done.
  11. (gdb) b test2
  12. Breakpoint 1 at 0x4005f3: file func_args.c, line 16.
  13. (gdb) r
  14. Starting program: /root/Project/programing/C/a.out
  15. 100
  16. 200
  17. 300

  18. Breakpoint 1, test2 (s=0x400714 "hello world") at func_args.c:16
  19. 16                p = (int *)&s;
  20. Missing separate debuginfos, use: debuginfo-install glibc-2.17-55.el7.x86_64
  21. (gdb) n
  22. 17                p++;
  23. (gdb) p p
  24. $1 = (int *) 0x7fffffffdfa8
  25. (gdb) p *(p+8)
  26. $2 = 100
  27. (gdb) p *(p+10)
  28. $3 = 200
  29. (gdb) n
  30. 18                printf("%d\n", *p);
  31. (gdb) p p
  32. $4 = (int *) 0x7fffffffdfac
复制代码
真心没想明白阿




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2