2013-01-17 16 views
1

我想知道C编译器(我试过gcc和clang)如何组织内部函数的参数列表。为此我做了包括以下两个文件的程序,例如:函数转换和函数参数列表的内部表示C

foo.c的:

double foo (double (*f) (void *, double), void * arg, double x) 
{ 
    return f (arg, x); 
} 

的main.c:

#include <stdio.h> 

extern double foo (double (*) (double), double); 

double bar (double x) 
{ 
    return x + x; 
} 

int main() 
{ 
    double x = foo (&bar, 2); 
    printf ("%g\n", x); 
    return 0; 
} 

,并与参数列表和身体玩foo.c中的foo。当foo.c与main.c中的定义不一致时,程序的行为应该根据C标准来定义(如果我理解正确的话)。

我已经尝试了几种变体(包括上面的一个),用于该程序将打印4. 的更奇特的一个是

double foo (double x, double (*f) (char, double, int), int r) 
{ 
    char a; 
    return f (a, x, r); 
} 

。不过,如果我会尝试像

double foo (double x, double (*f) (char, double, double), double y) 
{ 
    char a; 
    return f (a, y, x); 
} 

结果不会是4,但如果我写f(a, x, y)相反,我再次得到4。

该实验让我认为参数列表在内部表示为对应于不同类型的数组列表,其中关于不同类型参数顺序的信息将丢失。例如,参数列表 (char a, double x, int i, double y, char b)将类似于(char:{a, b}, int:{i}, double:{x, y}),并且投射到(double z, char c)将等于(char:{c = a}, double:{z = x}),其中我已将T:{}定义为T类型的数组。

所以:

我的解释是否正确?

此行为是否在某个地方标准化?

我可以依靠这样的行为多少?

此行为允许一些泛型编程。有人在实践中利用它吗?

谢谢!

+1

这是64位或32位代码? x86和AMD64之间的调用约定有所不同 - 前者将参数从左到右传递,而后者在[各种寄存器中按类型排序]传递一些参数(http://en.wikipedia.org/wiki/X86_calling_conventions#System_V_AMD64_ABI) 。它是将代码打包在一起的链接器,所有这些都是处理符号加载和重定位,而不是类型。 –

+0

@MathewHall它是x86_64,感谢您的链接。 –

+0

@MathewHall +1实际上,Linux上的x86_64调用约定负责观察到的行为。事实上,我使用的类型是“幸运的”。通过将double切换到int来重复测试应该是一团糟。再次感谢。如果有可能,我会接受你的答案! –

回答

3

如果函数的签名不匹配,则不能依赖任何行为。你的程序可以正常工作,崩溃或发射火箭导弹。这是未定义行为的定义。

如果您使用可变参数函数或者不描述参数的函数(即f(),不要与f(void)混淆),那么只能依赖某些东西。对于这些人来说,有关于争论推广和转换的规则。了解更多信息,请查找C99标准。

其余的都是纯粹的猜测。如果您知道处理器的汇编语言如何工作,则可以观察并推导出一些行为。例如,在x86中,调用函数和传递参数的标准方法已被很好地定义...

无论如何,依靠未定义的行为并不是一个好主意。