【问题标题】:Should I place the parameter storage class specifier in the function definition or in both the declaration and definition?我应该将参数存储类说明符放在函数定义中还是同时放在声明和定义中?
【发布时间】:2019-02-13 16:01:30
【问题描述】:

我正在将一些旧的 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;
}

这也正确构建:

int add(int x, int y);

int add(register int x, register int y)
{
   return x+y;
}

我想确保根据标准确实考虑了寄存器存储说明符(我的目标是使用非常旧的编译器进行编译,其中这个存储类说明符很重要)。两者都可以吗,这只是编码风格的问题吗?

【问题讨论】:

  • 目标编译器/系统是什么?
  • 看看这里:port70.net/~nsz/c/c11/n1570.html#6.7.6.3 唯一应该出现在参数声明中的存储类说明符是寄存器。
  • 存储类不是确定类型是否兼容的因素。因此,用 register 声明的函数的类型应该与没有 register 声明的函数的类型兼容,如果它们在其他方面兼容的话。
  • @EugeneSh。这不是个例。存储类从不包含在类型信息中。 auto int x;static int x;register int x; 都具有 int 类型。
  • @PSkocik 为什么不能包含autoregister 可以(好吧,static 没有意义)?

标签: c declaration definition c89


【解决方案1】:

关键规定是函数的每个声明都必须为其指定兼容的类型。这需要兼容的返回类型,并且对于像您这样包含参数列表的声明,每对对应参数的兼容类型。

那么问题就变成了存储类说明符是否区分类型。他们没有,尽管标准间接说明了这一点,因为在其对类型派生的讨论中省略了存储类说明符。因此,由对象声明中的存储类说明符指定的属性与该对象的类型是分开的。

另外,C89specifically says

参数声明的声明说明符中的存储类说明符,如果存在,将被忽略,除非声明的参数是函数定义的参数类型列表的成员之一。 p>

(强调添加)。函数定义是伴随函数体的声明,与前向声明相反,因此您的两个代码具有相同的语义。

无论有无寄存器存储类特定声明, 代码编译正确(我试过 gcc、VC++ 和 Watcom),但我不能 在 ISO/ANSI C89 标准中找到关于什么是 正确的做法,或者如果我只是把注册关键字放在 函数定义?

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

然而,

  1. register 关键字是一个遗物。编译器根本没有义务尝试将register 变量实际分配给寄存器,并且现代编译器在决定如何将变量分配给寄存器以及无论如何生成快速代码方面都比人类好得多。只要您在转换旧代码,我就会借此机会删除所有出现的register 关键字。

  2. C89 已过时。该标准的最新版本是C 2018; C 2011 被广泛部署; C99(从技术上讲,也是过时的)几乎随处可用。也许您有充分的理由以 C89 为目标,但您应该强烈考虑改为以 C11 或 C18 为目标,或至少 C99。

【讨论】:

  • 如上所述,我正在使用的编译器是 ANSI C89 兼容的,目前它的代码是 K&R,所以我正在尝试将代码“现代化”一点......然后引导它,它确实在其代码生成器中使用了 register 关键字。
  • 我认为,就像下面给出的其他建议一样,安全的赌注是在定义和声明中都有寄存器说明符,这就是我所做的,我只是想确保它是合乎逻辑的选择。
  • @CarlEricCodere,了解我所说的关于register 的内容很重要。关键不是编译器一定不会或不应该关注它,而是如果他们这样做,那么更有可能导致他们发出更糟糕的代码而不是更好的代码。 90 年代及以后的编译器在有效地将数据分配给寄存器方面比 70 年代的编译器要好得多。他们在这方面不需要程序员的帮助,所以通常最好的情况是使用register 关键字是不必要的冗余。
  • 但是,如果您确实保留了 register 关键字,那么可以,将它们放在声明和定义中是最好和最干净的。
【解决方案2】:

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

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

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

当我这样做时:

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
}

然后&B 编译,但&A 不编译,所以只有定义 中的限定符才算数。

我认为,如果您确实需要这些 register,最好的办法是在两个地方都使用它(原型中的 register 理论上可以修改调用的方式)。

【讨论】:

  • 有趣的观察...我想在这种情况下这是特定于编译器的。
  • @CarlEricCodere 我可能是错的,但我认为标准没有关于如何将具有不同存储分类器的原型与相互矛盾的定义结合起来的措辞,因此只剩下测试了。对于顶级限定符(如最后一个const int int const* const X),我认为规定只有定义中的那些用于正文。 (它们对函数的类型没有影响。)IRC 上次我检查时,__attributes(非标准扩展)和inline/_Noreturn 之类的东西往往会累积。
【解决方案3】:

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

应出现在参数声明中的唯一存储类说明符是register

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

由于您提到了 Watcom 和 C89,我假设您的目标是 x86-16。 x86-16 的典型调用约定(pascalstdcallcdecl)都要求将参数压入堆栈,而不是寄存器,所以我怀疑关键字实际上会修改参数的传递方式到呼叫站点的功能。

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

int __stdcall add2(register int x, register int y);

根据 stdcall 的要求,该函数以 _add2@4 的形式进入目标文件。 @4 表示在函数返回时要从堆栈中删除多少字节。在这种情况下使用ret imm16(返回调用过程并从堆栈中弹出imm16字节)指令。

add2 将在末尾有以下ret

ret 4

如果在调用站点没有将 4 个字节压入堆栈(即因为参数实际上在寄存器中),那么您的程序现在会有一个未对齐的堆栈并崩溃。

【讨论】:

  • 据我了解,除非我完全错了,否则参数中的存储类说明符只是意味着与参数关联的变量应该放在寄存器中(即使在代码开始执行后)例程),它不一定表示将使用寄存器调用约定来调用它,不是吗?
  • @CarlEricCodere 该标准实际上并没有说明该主题的任何内容 - 只是参数列表中允许使用关键字 register
【解决方案4】:

由于您在奇怪的平台上使用旧的编译器,有时只看编译器做了什么比假设它完全符合 C 规范更重要。

这意味着您希望通过编译器运行示例的每个变体,并将编译器选项设置为生成程序集而不是可执行文件。查看程序集,看看您是否可以判断它是否使用寄存器。在 gcc 中,这是 S 选项;例如:

gcc myfile.c -S -o myfile.s

【讨论】:

    【解决方案5】:

    来自 C17 标准,6.7.1 存储类说明符

    具有存储类的对象的标识符声明 说明符 register 建议访问对象的速度与 可能的。这些建议的有效程度是 实现定义。

    暗示编译器将尝试(或不依赖于编译器)加速参数访问,但不暗示对调用约定的任何修改(调用方基本上没有修改)。

    所以它应该存在于函数定义中,但在原型中无关紧要。

    【讨论】:

      猜你喜欢
      • 2014-01-17
      • 1970-01-01
      • 2020-09-28
      • 1970-01-01
      • 2016-11-25
      • 2018-07-05
      • 2021-10-23
      • 1970-01-01
      • 2017-02-16
      相关资源
      最近更新 更多