【问题标题】:Can local and register variables be declared extern?本地变量和寄存器变量可以声明为外部吗?
【发布时间】:2013-01-15 10:30:18
【问题描述】:

我一直想知道是否可以在本地声明 extern 和寄存器变量。如果可以,会施加什么限制?

【问题讨论】:

    标签: c extern


    【解决方案1】:

    局部变量在某些情况下可以声明为外部

    让我们阅读C99 N1256 standard draft

    标准将“局部变量”称为具有“块作用域”。

    6.7.1/5“存储类说明符”说:

    具有块作用域的函数的标识符声明不应具有除 extern 之外的显式存储类说明符。

    那么对于将extern 添加到局部变量意味着什么,6.2.2/4“标识符的链接”说:

    对于使用存储类说明符 extern 声明的标识符,在该标识符的先前声明可见的范围内,如果先前声明指定内部或外部链接,则后面声明的标识符的链接是相同的作为先前声明中指定的链接。如果前面的声明不可见,或者前面的声明没有指定链接,则标识符具有外部链接。

    让我们分解这些案例。

    没有事先声明

    void f() {
        extern int i;
    }
    

    等同于:

    extern int i;
    void f() {}
    

    除了声明仅在 f 内可见。

    这是因为i 没有可见的先前声明。所以i 有外部链接(与全局变量相同的链接)。

    先前的声明未指定链接

    int i;
    void f() {
        extern int i;
    }
    

    等同于:

    void f() {
        extern int i;
    }
    

    因为前面的声明 int i 没有指定链接,因为第 6 段说:

    以下标识符没有链接:声明为对象或函数以外的任何标识符;声明为函数参数的标识符;没有存储类说明符 extern 声明的对象的块范围标识符。

    先前的声明指定内部或外部链接

    extern int i;
    void f() {
        extern int i;
    }
    

    等同于:

    extern int i;
    void f() {}
    

    和:

    static int i;
    void f() {
        extern int i;
    }
    

    等同于:

    static int i;
    void f() {}
    

    因为在这两种情况下,我们分别有一个先前可见的外部和内部 (static) 链接声明。

    初始化本地外部

    无效的 C:

    void f() {
        extern int i = 0;
    }
    

    因为块作用域声明有一个初始化。

    有效 C:

    extern int i = 0;
    void f() {}
    

    但可以说是不好的风格,因为相当于较短:

    int i = 0;
    void f() {}
    

    因为 6.7.8 初始化说:

    如果标识符的声明具有块范围,并且标识符具有外部或内部链接,则声明不应具有标识符的初始值设定项。

    【讨论】:

    • Necroposting,但是 - ISO/IEC 9899:1999 6.7.1/p5 不是在谈论块作用域变量,而是在谈论块作用域函数。就是说void f() { static void g() {} }无效,块作用域函数声明只能指定extern
    • @blelbach 谢谢。变量是否有类似的引用,或者答案是否错误?如果变量有类似的引用,您可以用它编辑答案吗?
    • 在“先前声明未指定链接”下,此答案显示int i; extern int i;extern int i; 相同。但是,它不是,因为行为是未定义的。 C 2018(以及 2011 和更早版本)6.7 3 给出的约束是,如果标识符没有链接,则在同一范围和命名空间中不得有超过一个声明,但 typedef 名称和标签有某些例外。由于int i;i 没有链接,因此在同一范围内不应有其他声明。当违反此约束时,行为未定义。
    • C 2018 条款 6.7 第 3 段说“如果标识符没有链接,则不应有多个标识符声明(在声明符或类型说明符中)具有相同的范围和相同的命名空间,除了: — 可以重新定义 typedef 名称以表示与当前相同的类型,前提是该类型不是可变修改的类型; — 可以按照 6.7.2.3 中的规定重新声明标签。”
    • 是的,将int i 移出函数会将其移至新范围。 (每个复合语句都开始一个新块 [定义函数的复合语句的块实际上从参数声明的开头开始],选择和迭代语句及其子语句也是如此。)但是,当 int i; 不在任何功能,它没有没有联动;它具有外部联系。对于没有链接后跟不违反约束的外部链接的示例,您可以使用void f(void) { int i; { extern int i; … } }
    【解决方案2】:
    1. 可以将局部变量声明为外部变量吗?

    没有。但是全局变量可以在本地声明为extern

    // file1.c
    int Count;
    
    // file2.c
    void foo(void) {
      extern int Count;
      Count++;
    }
    
    1. 寄存器变量可以声明为外部吗?

    没有。变量可能不是externregister

    C11 dr 6.7.1 存储类说明符
    1 存储类说明符:
    typedef
    extern
    static
    _Thread_local
    auto
    register
    约束
    2 至多,一个存储类说明符可以在声明说明符中给出 声明,除了_Thread_local 可能与staticextern 一起出现)

    【讨论】:

      【解决方案3】:

      6.9 C99 状态的外部定义:

      存储类说明符 auto 和 register 不应出现在声明中 外部声明中的说明符。

      【讨论】:

      • OP的问题段不是很清楚,但标题抓住了本质;我相信这并不能回答他/她的问题。
      【解决方案4】:

      您只能将全局变量定义为extern。告诉编译器(和链接器)它是在别处定义的。

      局部变量只存在于局部范围内,因为它是在堆栈或寄存器中创建的。当执行不在范围内(不再)时,堆栈被展开(因此可用空间再次可用)或寄存器用于其他用途,并且变量不存在(不再存在)。

      所以定义一个本地 extern 会很“奇怪”而且不可能(由于堆栈的使用)。

      【讨论】:

      • 从技术上讲,您不会“将其设为外部”,而是告诉编译器该变量未在此文件中定义?
      • 嗯...你是对的。您定义一个变量extern 来告诉编译器它已经在其他地方创建并且链接器需要在链接时找到它,因此可以使用它(即使没有在使用它的源文件中明确定义)。我更新了我的答案,让它更清楚!
      • 必须补充一点,只要在全局范围内的其他地方定义了变量,从函数范围访问像extern int a; 这样的变量当然是有效的。如果您只需要从一个函数访问它,则无需将 extern int a; 放在文件的全局范围内。
      • @Jite:(回答您的第一条评论)从技术上讲,我认为标准中的语言是声明导致名称具有外部链接。这实际上并不意味着它没有在此文件中定义,如果您愿意,您可以在 extern 声明中添加定义。这也是一件好事,否则在定义其全局变量之前,您无法将库的“自己的”头文件包含到库源文件中:-) 外部链接的含义是,如果两个不同的 TU 使用相同的名称,并且都具有外部链接,它们引用相同的对象/函数。
      • @SteveJessop:当然,感谢您的澄清。我的第一条评论实际上不是一个陈述,而是一个问题,所以感谢您的回答:)
      【解决方案5】:

      register variable这句话对我来说不是很清楚,所以我大胆猜测一下 OP 真正好奇的是什么,并将原来的问题改写为:Could local variables be declared with extern specifier?,由以下 sn-p 说明:

      int main() {
          extern int x; // Is this OK?
          return 0;
      }
      

      答案是肯定的。

      范围(可见性)和存储是两个独立且相互关联的概念。这里,x 是一个局部变量(作用域),它只在这个块中可见。 extern 指示存储,这意味着这只是一个声明,此变量在其他地方定义。会推荐 C 标准以供明确参考。

      至于省略的register 部分,我假设OP 表示一个带有register 存储类说明符的变量,例如register int x。那么同时指定registerextern是违法的。

      int main() {
          extern auto int x; // This is wrong.
          return 0;
      }
      

      At most, one storage-class specifier may be given in the declaration specifiers in a declaration, except that _Thread_local may appear with static or extern.

      对称问题是:使用全局或外部变量指定 autoregister 是否有效,这正是 Alexey Frunze 的答案。

      auto int x; // This is wrong.
      int main() {
          return 0;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-11-16
        • 2010-11-18
        • 1970-01-01
        • 2012-09-24
        • 2023-03-26
        相关资源
        最近更新 更多