调用需要带结构的独立参数的C ++函数是否安全?

时间:2016-01-27 08:06:59

标签: c++ struct compilation

基本上,执行以下操作是安全的:

我们有一个简单的函数调用接受任意数量的参数:

void simpleCall(int x, int y, int z, int a, int l, int p, int k)
{
    printf("%i %i %i %i %i %i %i\n", x, y, z, a, l, p, k);
}

我们创建一个映射到参数的结构:

struct simpleArgs
{
    int x;
    int y;
    int z;
    int a;
    int l;
    int p;
    int k;
};

我们使用我们想要传递的参数初始化结构,转换函数以便编译,并传递结构代替所有参数:

int main()
{
    simpleArgs a;
    a.x = 1;
    a.y = 2;
    a.z = 3;
    a.a = 4;
    a.l = 5;
    a.p = 6;
    a.k = 7;

    ((void(*)(simpleArgs))simpleCall)(a);

    return 0;
}

节目打印:

1 2 3 4 5 6 7

这个特殊的例子没有任何意义(我们可以很容易地正常地调用函数),但是它是为了说明这个概念而编写的。因为struct是通过值传递的,所以这个编译方式与通过值传递每个参数相同吗?标准调用将参数放在堆栈上作为一个组,它应该与我们在这里看到的相同?在什么条件下通过寄存器传递参数?

看起来这样的事实可能是cdecl x86的侥幸,我测试过的编译器,以及我选择的参数类型。这似乎是如此脆弱,如果你选择一些非16位对齐的变量类型并且没有显式修改结构来对齐它,那么期待一个SIGSEV。

2 个答案:

答案 0 :(得分:3)

传递单个参数并将一个参数作为struct传递通常被视为不同的事物。例如,没有任何说法struct只是像它一样传递:

 struct X x;
 ... fill in x...
 func(x);

相反,有时会变成:

 struct X x;
 ... fill in x...
 struct X tmp = x;
 func(&tmp);

(换句话说,参数的副本是在调用站点进行的,然后作为地址传递,而不是在函数内部或实际调用期间 - 巧合的是,这就是我的Pascal编译器执行此类传递的方式)

有时结构的大小将决定整个结构是加载到寄存器中还是作为内存中的副本传递(其中各个参数将是寄存器和内存的混合)。参数的顺序可能与结构存储在内存中的顺序不同。

绝对没有一件事表明这个应该工作。它可能是偶然的。如果您决定更改编译器优化级别或编译不同的处理器类型,它也可能会中断。

答案 1 :(得分:1)

它绝对不是可移植的,即使它现在有效,如果您要将代码移植到不同的平台,这可能会在以后出现。

更好的解决方案是简单地重载这个函数:

inline void simpleCall(simpleArgs args){
    simpleCall(args.x,args.y,args.z,args.a,args.l,args.p,args.k);
}

int main(){
    simpleArgs a = {1,2,3,4,5,6,7};

    simpleCall(a);

    return 0;
}

由于函数的简单性和inline关键字的优化,所以执行的实际代码就好像你刚刚直接调用了函数一样。