变量的声明和定义 搞清楚“声明”(declaration)和“定义”(definition)之间的区别在理解C语言的过程中非常关键。 “声明”仅仅是告诉编译器某个标识符是:变量(什么类型)还是函数(参数和返回值是什么)。要是在后面的代码中出现该标识符,编译器就知道如何处理。记住最重要的一点:声明变量不会导致编译器为这个变量分配存储空间。 C语言专门有一个关键字(keyword)用于声明变量或函数:extern。带有extern的语句出现时,编译器将只是认为你要告诉它某个标识符是什么,除此之外什么也不会做(直接变量初始化除外)。 先来试一下: /*Example C code*/ extern int a; int main(void) { extern int b; a = 1; b = 2; return 0; } $gcc test.c 马上你会收到两条出错消息: undefined reference to “a” undefined reference to “b” 因为两条extern语句仅仅是声明变量,编译器虽然知道a和b是什么类型的变量,但在链接的时候却找不到它们的地址(因为变量没有被定义,所以编译器没有为它们分配存储空间),于是就出错了。 不妨看看汇编代码: $gcc –S test.c $cat test.s .globl main .type main, @function main: pushl %ebp movl %esp, %ebp subl $8, %esp andl $-16, %esp movl $0, %eax subl %eax, %esp movl $1, a movl $2, b movl $0, %eax leave ret 可见,编译器根据变量的声明已经知道如何处理外部变量a和b,但由于没有定义变量,所以汇编代码中没有由“.comm”开头的语句为外部变量分配存储空间,导致最后链接程序找不到有效的符号而报错。 说完“声明”,现在说“定义”。 有了对比就很容易明白,定义变量意味着不仅告诉编译器变量的类型,而且编译器同时必须为变量分配空间。定义变量的同时还可以初始化变量,例如: int a = 9; char c = ‘A’; 在函数里面定义、初始化内部变量已经在前面的文章中讨论过,现在探讨一下外部变量的定义和初始化。 首先要搞清楚编译器在什么情况下将语句认为是定义,什么情况下认为是声明。这里给出若干原则: #1 带有初始化的语句是定义 例如: int a = 1; //定义 #2 带有extern的语句是声明(除非对变量进行初始化) 例如: extern int a; //声明 extern int b = 2; //定义 #3 既没有初始化又没有extern的语句是“暂时定义”(tentative definition) 例如: int a; //暂时定义 C语言中,外部变量只能被(正式)定义一次: int a = 0; int a = 0; //错误!重复定义 又或者: int a = 0; double a = 0.1; //错误!标识符a已经被使用 暂时定义有点特殊,因为它是暂时的,我们不妨这样看: 暂时定义可以出现无数次,如果在链接时系统全局空间没有相同名字的变量定义,则暂时定义“自动升级”为(正式的)定义,这时系统会为暂时定义的变量分配存储空间,此后,这些相同的暂时定义(加起来)仍然只算作是一个(正式)定义。 例如: /*Example C code*/ int a; //暂时定义 int a; //暂时定义 int main(void) { a = 1; return 0; } int a; //暂时定义 让我们看一下汇编代码: $gcc –S test.c $cat test.s .globl main .type main, @function main: ... movl $1, a ... .comm a, 4, 4 程序显示编译器只给变量“a”分配空间一次,尽管C程序中有3个暂时定义语句。 刚才讲到如果没有相同名字的外部变量定义,则暂时定义会自动变成定义,那么,如果有相同名字的外部变量定义呢?很简单,这时暂时定义的作用相当于声明。 例如: /*Example C code*/ int a; //暂时定义 int a; //暂时定义 int main(void) { a = 1; return 0; } int a = 0; //定义 这里因为定义了外部变量,所以最上面的两个暂时定义就相当于仅仅声明a是int变量。看看汇编代码。 .globl main .type main, @function main: ... movl $1, a ... .globl a //a是一个全局可见的符号 .data //表示在数据段分配空间(函数的代码会放在代码段) .align 4 //指示为a分配的地址必须是“4字节对齐” .type a, @object //告诉编译器a所代表的空间存放的是数据 .size a, 4 //表示分配给a的空间大小是4个字节 a: //正式给出符号a的位置 .long 0 //用数值“0”初始化符号a代表的那块存储空间 这里注意,即使不对a进行初始化(.long句),a:这句代码也仍然要出现,因为它告诉编译器分配空间,没有这句话编译器还是不会分配空间。 回忆一下暂时定义对应的汇编语句: .comm a, 4, 4 然后和上面的代码作一对比,相信很容易区分暂时定义和(正式)定义在汇编代码中的表示。
|