【问题标题】:Iterating through a function's arguments by using a pointer to the first one使用指向第一个参数的指针迭代函数的参数
【发布时间】:2016-11-28 15:03:13
【问题描述】:

我想知道以下 C 代码是否符合 C99 和/或 C11 标准:

void foo(int bar0, int bar1, int bar2) {
    int *bars = &bar0;

    printf("0: %d\n1: %d\n2: %d\n", bars[0], bars[1], bars[2]);
}

int main(int argc, char **argv) {
    foo(8, 32, 4);

    return 0;
}

此代码 sn-p 在使用 Visual Studio 2013 时按预期编译和运行并打印:

0:8
1:32
2:4

【问题讨论】:

  • 您是想满足您的好奇心,还是您认为这会解决问题?
  • 只是出于好奇,因为可变参数似乎使用这种技术来迭代它的参数。

标签: c pointers parameter-passing c99 c11


【解决方案1】:

不,附近没有。

C 标准不保证函数参数存储在连续的内存位置(或任何特定的顺序,就此而言)。由编译器和/或平台(架构)决定如何将函数参数传递给函数。

为了更清楚,甚至根本无法保证要传递的参数存储在内存中(例如堆栈)。他们也可以针对某些所有参数使用硬件寄存器(只要适用),以加快操作。例如,

  • PowerPC

    PowerPC 架构具有大量寄存器,因此大多数函数可以在寄存器中传递所有参数以进行单级调用。 [...]

  • MIPS

    32 位 MIPS 最常用的调用约定是 O32 ABI,它将前四个参数传递给寄存器 $a0-$a3 中的函数;随后的参数在堆栈上传递。 [...]

  • X86

    x86 架构与许多不同的调用约定一起使用。由于架构寄存器数量较少,x86 调用约定大多在堆栈上传递参数,而返回值(或指向它的指针)在寄存器中传递。

等等。检查full wiki article here

因此,在您的情况下,bars[0] 是一个有效 访问,但bars[1]bars[2] 是否有效,取决于底层环境(平台/编译器),完全。最好不要依赖您所期望的行为。

也就是说,只是为了挑剔,如果您不打算使用传递给 main() 的参数(如果有),您可以简单地将签名减少到 int main(void) {

【讨论】:

  • +1,但也许有人应该补充一点,在现代架构中,第一个函数参数根本不存储在内存中,而是通过硬件寄存器传递。或许也不是编译器做出决定,而是平台 API。
  • @JensGustedt 先生,在这方面添加了更多信息。现在好点了吗?
【解决方案2】:

不,它不遵守任何已发布的标准。参数和局部变量的存储方式以及存储位置取决于编译器。在一个编译器中可能起作用的东西可能在另一个编译器中不起作用,甚至在同一编译器的不同版本中也可能不起作用。

C 规范甚至没有提到堆栈,它指定的只是范围规则。

【讨论】:

  • 可变参数函数似乎也是标准的一部分——va_start、va_end 是标准库的一部分,而这正是这些宏所做的——检查堆栈参数。
  • @MichaelMoser 是的,但是这些宏的实现方式不在标准中。 C 编译器不需要堆栈(特别是因为它不在 C 规范中),它只是方便的实现细节。
【解决方案3】:

没有标准支持这一点。太淘气了。

数组索引和指针运算仅对数组有效。 (请注意一个小例外:您可以读取指针超过数组或标量,但不能引用它。)

【讨论】:

    猜你喜欢
    • 2017-06-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多