我应该在函数定义中还是在声明和定义中放置参数存储类说明符?

Car*_*ere 14 c declaration definition c89

我正在努力将一些旧的K&R代码移植到ANSI C,所以我正在编写缺少的函数原型声明.很多函数定义都有寄存器存储类的参数,但是我不确定函数原型中是否可以省略寄存器存储类说明符?

有和没有寄存器存储类特定声明,代码编译正确(我尝试了GCC,VC++和Watcom C).我在ISO/ANSI C89标准中找不到任何关于正确方法的信息 - 如果我只是将register关键字放在函数定义中就可以了吗?

int add(register int x, register int y); 

int add(register int x, register int y)
{
  return x+y;
}
Run Code Online (Sandbox Code Playgroud)

这也正确构建:

int add(int x, int y);

int add(register int x, register int y)
{
   return x+y;
}
Run Code Online (Sandbox Code Playgroud)

我想确保根据标准真正考虑寄存器存储说明符(我的目标是使用非常旧的编译器进行编译,其中此存储类说明符很重要).两者都好,这只是一个编码风格的问题,或不是?

Joh*_*ger 15

关键的规定是函数的每个声明都必须为它指定一个兼容的类型.这需要兼容的返回类型,并且对于诸如你的包含参数列表的声明,每对相应参数的兼容类型.

那么问题就在于存储类说明符是否区分了类型.虽然标准间接地通过省略类型派生的讨论中的存储类说明符,但它们没有这样做.因此,对象声明中的存储类说明符指定的属性与该对象的类型是分开的.

而且,C89 具体说

除非声明的参数是函数定义的参数类型列表的成员之一,否则将忽略参数声明的声明说明符中的存储类说明符(如果存在).

(重点补充).函数定义是一个伴随函数体的声明,而不是前向声明,因此您的两个代码具有相同的语义.

有和没有寄存器存储类特定声明,代码编译正确(我尝试过gcc,VC++和Watcom),我在ISO/ANSI C89标准中找不到任何关于正确方法的信息,或者它是否正常如果我只是将register关键字放在函数定义中?

就个人而言,我倾向于使每个前向声明与相应函数定义中的声明相同.如果函数定义本身正确,这绝不是错误的.

然而,

  1. register关键字的遗物.编译器没有义务做任何尝试将register变量实际分配给寄存器,现代编译器在决定如何将变量分配给寄存器以及无论如何生成快速代码时都比人类好得多.只要你转换旧代码,我就会借此机会删除register关键字的所有外观.

  2. C89已经过时了.该标准的最新版本是C 2018; C 2011被广泛部署; 几乎在任何地方都可以使用C99(技术上已经过时了).也许您有充分的理由将C89作为目标,但您应该强烈考虑使用C11或C18,或至少 C99.


Gov*_*mar 6

C89标准确实这样说(第3.5.4.3节外部定义):

唯一存在于参数声明中的存储类说明符是register.

因此,虽然register允许作为函数参数存储类说明符,但我仍然认为这是否值得尊重,实际上取决于函数的体系结构和调用约定.

既然你提到了Watcom和C89,我假设你的目标是x86-16.x86-16(pascal,stdcallcdecl)的典型调用约定都要求将参数推送到堆栈而不是寄存器中,因此我怀疑该关键字实际上会修改参数在调用站点传递给函数的方式.

考虑一下,您有以下函数定义:

int __stdcall add2(register int x, register int y);
Run Code Online (Sandbox Code Playgroud)

该函数_add2@4根据stdcall的要求进入目标文件.@ 4表示在函数返回时从堆栈中删除的字节数.的ret imm16(返回到调用堆栈从程序和弹出imm16字节)指令在此情况下使用.

add2然后ret在最后会有以下内容:

ret 4
Run Code Online (Sandbox Code Playgroud)

如果4个字节没有在调用站点的堆栈上被推送(即因为参数实际上在寄存器中),那么您的程序现在有一个未对齐的堆栈和崩溃.


PSk*_*cik 6

根据gcc和clang的经验,register函数参数上的存储类与params上的顶级限定符的行为相同:只有定义中的存储类(不是以前的原型)计数.

(对于顶级限定符,当考虑类型兼容性时它们也被丢弃,即,void f(int);并且void f(int const);是兼容的原型,但存储类不是类型的一部分,因此类型兼容性首先不是它们的问题)

从C程序员的角度来看,C中唯一可观察到的结果register是编译器不会让你获取声明对象的地址.

当我做:

void f(int A, int register B);

void f(int register A, int B) 
{
    /*&A;*/ //doesn't compile => A does have register storage here
    &B; //compiles => B doesn't have register storage here;
        //the register from the previous prototype wasn't considered
}
Run Code Online (Sandbox Code Playgroud)

然后&B编译,但&A不编译,因此只有定义中的限定符才算数.

我认为,如果你确实需要这些register,你最好的选择是在两个地方一致地使用它(register在原型中理论上可以修改调用的方式).