声明和定义: scope和linkage、强符号和弱符号、初始化、隐式声明

声明分为引用性声明和定义性声明。一般而言(本文中也是如此),“声明”指引用性声明 ,“定义”指定义性声明。本文中示例代码的运行结果均为gcc环境下所得。

1 声明和定义的语法

1.1 对于变量

只要不用extern 修饰即为定义。

int a; //定义 弱符号
int b = 1; //定义并初始化 强符号

使用extern 修饰的变量,如果做了初始化则为定义,否则是一个声明。 但使用extern 修饰的变量做初始化,编译时会产生警告。

extern int a; //声明
extern int b = 1; //定义

编译结果:

 warning: 'b' initialized and declared 'extern'

声明或定义中如果没有类型则采用默认类型 ,并产生警告。当然这取决于你的编译选项设置。

a = 1; //定义

编译结果:

warning: data definition has no type or storage class
a = 1;
^
warning: type defaults to 'int' in declaration of 'a' [-Wimplicit-int]

1.2 对于函数

通过是否带有函数体区分定义或声明。

void test1() 
{//带有函数体 为定义

}
void test2(); //没有函数体 为声明

2 scope和linkage

2.1 scope

C99 6.2.1 规定了四个scope :
blockscope
局部代码块范围,即{ }内。
function scope
标签(Labels)是唯一一类拥有function scope的名字。可以在它所出现的函数中的任何位置被使用(通过goto语句)。
function prototype scope
函数原型中声明的名字只在该原型结束前可见。比如下面的原型声明了3个名字(strDestination, numberOfElements 和 strSource);在原型结束处它们离开作用域。

errno_t strcpy_s( char *strDestination, size_t numberOfElements, const char *strSource );

file (global)scope
全局范围。

2.2 linkage

对象和函数名在编译单元之间的共享方式被称为linkage。

linkage说明
external linkage标识符在其他编译单元1可见
internal linkage标识符仅在本编译单元可见
no linkage标识符仅在它们的作用域内可见

internal linkage

  • 显式声明为static的对象、引用、函数或函数模板。
  • 使用了说明符const,但没有显式使用extern或提前声明为external linkage的对象或引用。
  • 匿名 union 中的数据成员。
  • 匿名命名空间中声明的标识符。
    external linkage
  • file scope中,没有使用static声明对象和函数的标识符。
    no linkage
  • 函数参数
  • block scope中未声明为 extern 或 static 的名字。
  • typedef语句中声明的名字。

2.3 static 用法总结

  • 全局变量定义时加上static修饰,表示该变量为静态全局变量。作用域为当前文件。
  • 任意函数的定义或声明中包含了static修饰,表示该函数为静态函数,只能在本文件中被调用。
  • 局部变量定义时,带有static关键字,表示其为静态局部变量,只被初始化一次,之后每次调用函数时,该变量值为上次函数退出时的值。即,改变量的生存周期被扩展到整个程序运行时间段内。

2.4 枚举,结构体,联合体的类型定义可以在其他文件中声明么?

似乎没有相应的语法,只能在其他文件中重新定义相同的类型。
多个文件使用相同的类型定义时,可以定义在.h文件中,然后在.c文件中include 。如果你定义的类型只在本.c文件中使用那么直接写在本.c文件中就好。

3 强符号和弱符号

在编程中会发现,多个文件中重复定义同一全局变量,在链接时不一定会报错。这是为何呢? 在《程序员的自我修养-链接、装载与库》的强符号与弱符号一节中可以找到这个问题的原因。

对于C/C++语言来说,编译器默认函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号。也可以通过GCC的“attribute((weak))””来决定任何一个强符号为弱符号。强符号和弱符号是针对符号的定义来说的,不针对付符号的引用。

引用也可以分为强引用和弱引用。对外部目标文件的符号引用在目标文件被最终链接成可执行文件时,它们需要被正确决议,如果没有找到改符号的定义,链接器会报符号未定义错误,这种被称为强引用。而对于弱引用,如果未定义,链接器默认其为0或者一特殊值,以便程序代码能够识别。

3.1 链接器处理规则

针对强弱符号的概念,链接器按如下规则处理多次定义的全局符号:

  1. 不允许强符号多次定义,如果存在则报符号重复定义错误。
  2. 如果一个符号在某个目标文件2中是强符号,在其他文件中是弱符号,那么选强符号
  1. 如果一个符号在所有目标文件中都是弱符号,那么选择其中占用空间最大的一个。

3.2 如何避免重复定义

  1. 尽量不使用全局变量。
  2. 定义成static的,并封装函数,外部文件通过调用函数来使用。
  3. 定义时初始化,使其成为强符号。

GCC提供了一个选项,可以检查这类错误:-fno-common。

3.3 弱符号、弱引用和链接器的COMMON块

弱符号和弱引用主要用于库的链接过程。与连接器COMMON块概念联系紧密。。。。。。
待补充

4 初始化

全局变量或局部静态变量,如果没有初始化或初始化为 0,则编译后会被存放在目标文件的未初始化段(.bss段)。如果定义中有初始化,则被放在数据段(.data)或只读数据段(.rodata)。它们都是在程序运行之前被初始化的。

局部变量则在程序运行时才会进行初始化。在初始化前的值是什么,c语言标准中说由具体实现决定。一般而言下是这个变量地址之前储存的值。
不过即使你在定义过程中做了初始化也可能会出现一些奇怪的问题。比如以下例子:

int s= 1;
void test()
{
	switch(s){
		int a = 1;
		case 0 :
		    printf(" here !");
			int b = 1;
			break;
		case 1 :
			printf("a is %d b is %d\n",a,b);
			break;
		default :
			break;
	}
	return ;
}

变量a的作用域是在整个swtich{}块中,它被分配在栈上。由于switch在执行时是直接跳转到case执行的,变量a的初始化未被执行。所以a的值是一个不确定的值。
类似的在case 0: 中定义并初始化 变量。由于case 0:语句不会被执行到,变量b的值也是不确定的。
另外 case 是一个标签,在case :后边紧跟变量声明是会报错的。

int s = 1;
void test()
{
	switch(s){	
		case 0 :
			int a = 1;
			break;
		default :
			break;
	}
	return ;
}

编译结果:

error:a label can only be part of a statement and a declaration is not a statement

5 函数隐式声明

引用外部文件中的全局变量 必须显示声明该变量。否则编译器会报错。

error: 'xxx' undeclared (first use in this function)

对于函数则只会产生警告。

implicit declaration of function 'xxxx' [-Wimplicit-function-declaration]

在C89中,函数在调用前不一定非要声明。如果没有声明,那么编译器会自动按照一种隐式声明的规则去处理。较新的C规范(C99、C11)是不允许不声明直接用的。只是GCC默认还允许implicit function declaration功能。

如果调用外部文件的函数而没有声明,则gcc编译器会默认函数返回值得类型为int 型。如果函数原型的返回值不是int型。gcc编译器可能会产生类型不匹配的警告。不过对于标准库内建函数,gcc编译器会使用正确的返回值类型。

关于函数隐式声明的详细讨论可参考我之间转载的文章- C语言中的隐式函数声明


  1. 编译单元:一个源文件即一个编译单元。 ↩︎

  2. 目标文件:编译器编译源代码后生成的文件叫做目标文件。目标文件从结构上讲已经是可执行文件格式了,只是还没有经过链接过程其中有些符号或地址还没被调整。 ↩︎

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值