V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
YUCOAT
V2EX  ›  程序员

关于C语言参数入栈顺序的问题

  •  
  •   YUCOAT · 2013-09-11 22:39:30 +08:00 · 3639 次点击
    这是一个创建于 4090 天前的主题,其中的信息可能已经有所发展或是发生改变。
    源码如下:



    我的想法是,创建一个不参数定长的函数

    void arg(int arg_count, ...)

    调用者往往该函数内传入N + 1个参数,其中第一个参数的值为参数的个数。

    arg()的功能就是将传入的int型参数输出到屏幕上。

    我这样编写的依据是,C语言的参数是从右往左入栈的,然后知道第一个参数的地址和参数的个数之后就能将它所有的参数输出。

    但是不巧的是,程序的执行结果是

    0x7fff9e7f738c:4
    0x7fff9e7f7390:-1635814512
    0x7fff9e7f7394:32767
    0x7fff9e7f7398:0
    0x7fff9e7f739c:4

    这是什么原因呢?
    13 条回复    1970-01-01 08:00:00 +08:00
    YUCOAT
        1
    YUCOAT  
    OP
       2013-09-11 22:43:11 +08:00
    编译器是Gcc 4.6x,不知道Windows下的vs编译器的结果是什么。

    我阅读了一下stdarg.h的源码,想看看va_list/va_start的实现,但是被那些宏给绕晕了
    SErHo
        2
    SErHo  
       2013-09-11 22:47:41 +08:00   ❤️ 1
    在Windows下使用gcc就是正常的,32位。
    SErHo
        3
    SErHo  
       2013-09-11 22:50:24 +08:00
    Windows VS2012 也正常。
    timonwong
        4
    timonwong  
       2013-09-11 22:54:04 +08:00   ❤️ 1
    x86_64头几个是使用的通用寄存器,其它的才是栈上的。
    http://en.wikipedia.org/wiki/X86_calling_conventions
    bengol
        5
    bengol  
       2013-09-12 00:01:29 +08:00 via iPad
    @timonwong 你这个是fastcall, lz的是cdecl
    VYSE
        6
    VYSE  
       2013-09-12 01:57:40 +08:00
    楼主你是不是用了64位编译?内存地址都超了,64下就得用__int64一个个遍历了
    timonwong
        7
    timonwong  
       2013-09-12 07:56:09 +08:00 via iPhone
    @bengol x86_64 有规定的abi,不是cdecl
    onemoo
        8
    onemoo  
       2013-09-12 08:46:55 +08:00
    LZ试试不用这段“int* int_ptr = &count;”来取地址。
    而是用汇编从%ebp开始向高址位去找找。

    或者直接将你的这段代码汇编输出,看看int_ptr是不是指向调用帧的压栈区。

    还有,不要用任何优化...
    jiumingmao
        9
    jiumingmao  
       2013-09-12 09:08:32 +08:00
    用 va_start va_arg va_end 试试
    LetFoxRun
        10
    LetFoxRun  
       2013-09-12 09:17:15 +08:00
    pezy
        11
    pezy  
       2013-09-12 09:30:12 +08:00
    完全取决于你用多少位的编译器。在Windows下用TDM-GCC 4.7.1 32位编译器,按照楼主的代码,就可以得到想要的答案。如果用64位,则需要将++int_ptr改为int_ptr += 2了,也是可以得到楼主想要的结果的。

    我觉得这至少能证明楼主你的思路是对的。

    当然比较成熟的方案还是使用va_start va_arg va_end了。
    Jex
        12
    Jex  
       2013-09-12 11:52:33 +08:00   ❤️ 1
    其实GCC在处理函数调用时,参数并不总是用栈传递的。

    之前我回答过一个类似的问题:http://zhi.hu/VQVA

    这里有不同的函数参数传递形式的列表:http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html

    楼上有说什么fastcall与cdecl。楼主代码显然是在64位平台上编译的。
    通过`gcc -S` 命令输出汇编代码,就会看到实际上参数是通过寄存器传递的
    ```
    main:
    .LFB1:
    .cfi_startproc
    pushq %rbp
    .cfi_def_cfa_offset 16
    movq %rsp, %rbp
    .cfi_offset 6, -16
    .cfi_def_cfa_register 6
    movl $50, %r8d
    movl $40, %ecx
    movl $30, %edx
    movl $20, %esi
    movl $4, %edi
    movl $0, %eax
    call arg
    movl $0, %eax
    leave

    ```
    johnnyb
        13
    johnnyb  
       2013-09-15 00:44:07 +08:00
    看了一下汇编,确如楼上所说,是通过寄存传递的。va_start/va_end/va_arg 的实现也被"隐藏"起来了:

    #define va_start(ap, param) __builtin_va_start(ap, param)
    #define va_end(ap) __builtin_va_end(ap)
    #define va_arg(ap, type) __builtin_va_arg(ap, type)

    这几个 __builtin 是怎么实现的?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1279 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 17:45 · PVG 01:45 · LAX 09:45 · JFK 12:45
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.