【发布时间】:2013-01-15 10:30:18
【问题描述】:
我一直想知道是否可以在本地声明 extern 和寄存器变量。如果可以,会施加什么限制?
【问题讨论】:
我一直想知道是否可以在本地声明 extern 和寄存器变量。如果可以,会施加什么限制?
【问题讨论】:
局部变量在某些情况下可以声明为外部
让我们阅读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 初始化说:
如果标识符的声明具有块范围,并且标识符具有外部或内部链接,则声明不应具有标识符的初始值设定项。
【讨论】:
void f() { static void g() {} }无效,块作用域函数声明只能指定extern。
int i; extern int i; 与extern int i; 相同。但是,它不是,因为行为是未定义的。 C 2018(以及 2011 和更早版本)6.7 3 给出的约束是,如果标识符没有链接,则在同一范围和命名空间中不得有超过一个声明,但 typedef 名称和标签有某些例外。由于int i; 的i 没有链接,因此在同一范围内不应有其他声明。当违反此约束时,行为未定义。
int i 移出函数会将其移至新范围。 (每个复合语句都开始一个新块 [定义函数的复合语句的块实际上从参数声明的开头开始],选择和迭代语句及其子语句也是如此。)但是,当 int i; 不在任何功能,它没有没有联动;它具有外部联系。对于没有链接后跟不违反约束的外部链接的示例,您可以使用void f(void) { int i; { extern int i; … } }。
没有。但是全局变量可以在本地声明为extern。
// file1.c
int Count;
// file2.c
void foo(void) {
extern int Count;
Count++;
}
没有。变量可能不是extern 和register。
C11 dr 6.7.1 存储类说明符
1 存储类说明符:typedefexternstatic_Thread_localautoregister
约束
2 至多,一个存储类说明符可以在声明说明符中给出 声明,除了_Thread_local可能与static或extern一起出现)
【讨论】:
6.9 C99 状态的外部定义:
存储类说明符 auto 和 register 不应出现在声明中 外部声明中的说明符。
【讨论】:
您只能将全局变量定义为extern。告诉编译器(和链接器)它是在别处定义的。
局部变量只存在于局部范围内,因为它是在堆栈或寄存器中创建的。当执行不在范围内(不再)时,堆栈被展开(因此可用空间再次可用)或寄存器用于其他用途,并且变量不存在(不再存在)。
所以定义一个本地 extern 会很“奇怪”而且不可能(由于堆栈的使用)。
【讨论】:
extern 来告诉编译器它已经在其他地方创建并且链接器需要在链接时找到它,因此可以使用它(即使没有在使用它的源文件中明确定义)。我更新了我的答案,让它更清楚!
extern int a; 这样的变量当然是有效的。如果您只需要从一个函数访问它,则无需将 extern int a; 放在文件的全局范围内。
extern 声明中添加定义。这也是一件好事,否则在定义其全局变量之前,您无法将库的“自己的”头文件包含到库源文件中:-) 外部链接的含义是,如果两个不同的 TU 使用相同的名称,并且都具有外部链接,它们引用相同的对象/函数。
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。那么同时指定register和extern是违法的。
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.
对称问题是:使用全局或外部变量指定 auto 或 register 是否有效,这正是 Alexey Frunze 的答案。
auto int x; // This is wrong.
int main() {
return 0;
}
【讨论】: