当 static
用于不同的上下文环境中时,static
关键字具有不同的意思。
本文对C语言中的static
、extern
关键字做了总结。
关于变量
变量类型 | 声明的位置 | 是否在栈中 | 作用域 | 如果声明为static |
---|---|---|---|---|
全局 | 所有代码块之外 | 否 | 从声明处到文件尾 | 不允许从其他源文件访问 |
局部 | 代码块起始处 | 是 | 整个代码块 | 变量不存储于堆栈中,它的值在程序整个执行期一直保持 |
形式参数 | 函数头部 | 是 | 整个函数 | 不允许 |
注意
- 全局变量初始化时默认初始化为0。
extern
只是对变量的声明,不是定义;但若对加extern
关键字的变量赋值,则extern
关键字将被忽略。
实验
实验环境:gcc 12.2.0,Arch Linux
使用gcc -o main main.c test.c
命令进行编译。
1. 两个源文件里的变量均不加static、extern
源代码如下。
main.c
#include <stdio.h>
int a;//或int a = ?,?为任意整数
extern void test();
int main(){
printf("The address of a is %p\n", &a);
test();
return 0;
}
test.c
#include <stdio.h>
int a;//或int a = ?,?为任意整数
void test(){
printf("The address of a in test.c is %p\n", &a);
}
编译时报错。
原因:变量重名,在链接时出现冲突。
/usr/bin/ld: /tmp/ccLB1iOH.o:(.bss+0x0): multiple definition of `a'; /tmp/ccNDOOOV.o:(.bss+0x0): first defined here
collect2: 错误:ld 返回 1
2. 在一个源文件的变量a前加extern
此处以main.c
的变量a前加extern
为例。
main.c
#include <stdio.h>
extern int a;
extern void test();
int main(){
printf("The address of a is %p\n", &a);
test();
return 0;
}
test.c
#include <stdio.h>
int a;//或int a = ?,?为任意整数
void test(){
printf("The address of a in test.c is %p\n", &a);
}
顺利通过编译,输出如下:
The address of a is 0x557b52d6901c
The address of a in test.c is 0x557b52d6901c
3.在一个源文件的变量a前加extern,并对其赋初值
此处以main.c
的变量a前加extern
并赋初值为例。
main.c
#include <stdio.h>
extern int a = 1;
extern void test();
int main(){
printf("The address of a is %p\n", &a);
test();
return 0;
}
test.c
#include <stdio.h>
int a;//或int a = ?,?为任意整数
void test(){
printf("The address of a in test.c is %p\n", &a);
}
编译时报错。
main.c:2:12: 警告:‘a’已初始化,却又被声明为‘extern’
2 | extern int a = 1;
| ^
/usr/bin/ld: /tmp/cchrQvYJ.o:(.bss+0x0): multiple definition of `a'; /tmp/cc6uWNF1.o:(.bss+0x0): first defined here
collect2: 错误:ld 返回 1
原因:a在test.c
中被初始化为0,但在main.c
中又被初始化为1。
4.两个源文件的变量a前均加extern
main.c
#include <stdio.h>
extern int a;
extern void test();
int main(){
printf("The address of a is %p\n", &a);
test();
return 0;
}
test.c
#include <stdio.h>
extern int a;
void test(){
printf("The address of a in test.c is %p\n", &a);
}
编译时报错。
/usr/bin/ld: /tmp/cc56fDo6.o: warning: relocation against `a' in read-only section `.text'
/usr/bin/ld: /tmp/ccKl0ioB.o: in function `main':
main.c:(.text+0x7): undefined reference to `a'
/usr/bin/ld: /tmp/cc56fDo6.o: in function `test':
test.c:(.text+0x7): undefined reference to `a'
/usr/bin/ld: warning: creating DT_TEXTREL in a PIE
collect2: 错误:ld 返回 1
原因:两个源文件都认为变量a在其他源文件中,因此两个变量a均没有被初始化。
5.两个源文件的变量a前均加extern,并对其中一个变量a赋初值
main.c
#include <stdio.h>
extern int a;
extern void test();
int main(){
printf("The address of a is %p\n", &a);
test();
return 0;
}
test.c
#include <stdio.h>
extern int a = 1;
void test(){
printf("The address of a in test.c is %p\n", &a);
}
编译时有警告,但编译通过了。
警告内容:
test.c:2:12: 警告:‘a’已初始化,却又被声明为‘extern’
2 | extern int a = 1;
|
输出两个相同的地址:
The address of a is 0x55fda963e018
The address of a in test.c is 0x55fda963e018
此时test.c
中的extern
关键字被忽略,变量a被初始化为1。
6.至少有一个源文件的变量a前加static
main.c
#include <stdio.h>
static int a;//或static int a = ?,其中?为任意整数
extern void test();
int main(){
printf("The address of a is %p\n", &a);
test();
return 0;
}
test.c
#include <stdio.h>
int a;//或int a = ?、static int a = ?,其中?为任意整数
void test(){
printf("The address of a in test.c is %p\n", &a);
}
顺利通过编译,输出两个不同的地址:
The address of a is 0x5597aed8f01c
The address of a in test.c is 0x5597aed8f020
7.在一个源文件的变量a前加static,另一个源文件的变量a前加extern
此处以main.c
的变量a前加extern
、test.c
的变量a前加static
为例。
main.c
#include <stdio.h>
extern int a;
extern void test();
int main(){
printf("The address of a is %p\n", &a);
test();
return 0;
}
test.c
#include <stdio.h>
static int a;//或static int a = ?,其中?为任意整数
void test(){
printf("The address of a in test.c is %p\n", &a);
}
编译时报错。
/usr/bin/ld: /tmp/ccxzPcGx.o: warning: relocation against `a' in read-only section `.text'
/usr/bin/ld: /tmp/ccxzPcGx.o: in function `main':
main.c:(.text+0x7): undefined reference to `a'
/usr/bin/ld: warning: creating DT_TEXTREL in a PIE
collect2: 错误:ld 返回 1
原因:main.c
认为变量a在其他源文件中,但由于test.c
中的变量a前有static
关键字,导致test.c
中的变量a无法被main.c
看到。
8.在一个源文件的变量a前加static,另一个源文件的变量a前加extern,并对加extern的变量a赋初值
此处以main.c
的变量a前加extern
、test.c
的变量a前加static
为例。
main.c
#include <stdio.h>
extern int a = 1;
extern void test();
int main(){
printf("The address of a is %p\n", &a);
test();
return 0;
}
test.c
#include <stdio.h>
static int a;//或static int a = ?,其中?为任意整数
void test(){
printf("The address of a in test.c is %p\n", &a);
}
编译时有警告,但编译通过了。
警告内容:
main.c:2:12: 警告:‘a’已初始化,却又被声明为‘extern’
2 | extern int a = 1;
|
输出两个不同的地址:
The address of a is 0x56332eb49018
The address of a in test.c is 0x56332eb49020
此时main.c
中的extern
关键字被忽略,main.c
中的变量a被初始化为1。
关于函数
当它用于函数定义时,或用于代码块之外的变量声明时,static
关键字用于修改标识符的链接属性(从external变成internal),但标识符的存储类型和作用域不受影响。用这种方式声明的函数只能在声明它们的源文件中访问。
注意
- 对于函数而言,没有加
static
关键字,就相当于是用了extern
。也就是说,对于函数来说,extern
关键字可加可不加。 - 函数的定义只能有一次,但声明可以有多次。
- 用
#include
引用头文件,与static
/extern
关键字无关。
实验
实验环境同上。
使用gcc -o main main.c plus.c
命令进行编译。
1.两个源文件里的函数均不加static,一个源文件里函数为声明,另一个源文件里函数为定义
main.c
#include <stdio.h>
int plus(int a, int b);//或extern int plus(int a, int b);
void test();
int main() {
printf("%d\n", plus(1, 2));
printf("The address of plus in main.c is %p\n", &plus);
test();
return 0;
}
plus.c
#include <stdio.h>
int plus(int a, int b) {//或extern int plus(int a, int b)
return a + b;
}
void test() {
printf("%d\n", plus(1, 2));
printf("The address of plus in plus.c is %p\n", &plus);
}
顺利通过编译,输出两个相同的地址。
3
The address of plus in main.c is 0x557598941191
3
The address of plus in plus.c is 0x557598941191
2.两个源文件里的函数均不加static,且均对plus函数进行定义
main.c
#include <stdio.h>
int plus(int a, int b) {//或extern int plus(int a, int b)
return a + b;//或定义成其他实现
}
void test();
int main() {
printf("%d\n", plus(1, 2));
printf("The address of plus in main.c is %p\n", &plus);
test();
return 0;
}
plus.c
#include <stdio.h>
int plus(int a, int b) {//或extern int plus(int a, int b)
return a + b;
}
void test() {
printf("%d\n", plus(1, 2));
printf("The address of plus in plus.c is %p\n", &plus);
}
编译时报错。
/usr/bin/ld: /tmp/ccCEIzPg.o: in function `plus':
plus.c:(.text+0x0): multiple definition of `plus'; /tmp/ccs1aZ09.o:main.c:(.text+0x0): first defined here
collect2: 错误:ld 返回 1
原因:对同一个函数进行了多次定义。
3.在一个源文件里对plus函数进行定义,并在前面加static,另一个源文件里对plus函数进行声明,前面不加static
main.c
#include <stdio.h>
int plus(int a, int b);//或extern int plus(int a, int b);
void test();
int main() {
printf("%d\n", plus(1, 2));
printf("The address of plus in main.c is %p\n", &plus);
test();
return 0;
}
plus.c
#include <stdio.h>
static int plus(int a, int b) {
return a + b;
}
void test() {
printf("%d\n", plus(1, 2));
printf("The address of plus in plus.c is %p\n", &plus);
}
编译时出错。
/usr/bin/ld: /tmp/cc44QBcC.o: in function `main':
main.c:(.text+0xf): undefined reference to `plus'
/usr/bin/ld: main.c:(.text+0x2c): undefined reference to `plus'
collect2: 错误:ld 返回 1
原因:main.c
中plus
函数只有声明没有定义,且plus.c
中plus
函数加了static
关键字,函数定义无法被main.c
看到。
4.在一个源文件里对plus函数进行声明,并在前面加static,另一个源文件里对plus函数进行定义,不加static
main.c
#include <stdio.h>
static int plus(int a, int b);
void test();
int main() {
printf("%d\n", plus(1, 2));
printf("The address of plus in main.c is %p\n", &plus);
test();
return 0;
}
plus.c
#include <stdio.h>
int plus(int a, int b) {//或extern int plus(int a, int b)
return a + b;
}
void test() {
printf("%d\n", plus(1, 2));
printf("The address of plus in plus.c is %p\n", &plus);
}
编译时有警告,但编译通过了。
警告内容:
main.c:2:12: 警告:‘plus’使用过但从未定义
2 | static int plus(int a, int b);
| ^~~~
输出两个相同的地址:
3
The address of plus in main.c is 0x55f26eaf2191
3
The address of plus in plus.c is 0x55f26eaf2191
原因:main.c
中虽在plus
函数的声明前加了static
,但在main.c
中,不存在plus
函数的定义。因此在main.c
中调用plus
函数时,调用的仍是plus.c
中的plus
函数。
这种情况比较复杂,配合汇编代码会更直观:
main.S
.file "main.c"
.text
.section .rodata
.LC0:
.string "%d\n"
.align 8
.LC1:
.string "The address of plus in main.c is %p\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $2, %esi
movl $1, %edi
call plus@PLT //此处调用了plus函数
movl %eax, %esi
leaq .LC0(%rip), %rax
movq %rax, %rdi
movl $0, %eax
call printf@PLT
movq plus@GOTPCREL(%rip), %rax
movq %rax, %rsi
leaq .LC1(%rip), %rax
movq %rax, %rdi
movl $0, %eax
call printf@PLT
movl $0, %eax
call test@PLT
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 12.2.0"
.section .note.GNU-stack,"",@progbits
plus.S
.file "plus.c"
.text
.globl plus
.type plus, @function
plus: //plus 函数的标号
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl -4(%rbp), %edx
movl -8(%rbp), %eax
addl %edx, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size plus, .-plus
.section .rodata
.LC0:
.string "%d\n"
.align 8
.LC1:
.string "The address of plus in plus.c is %p\n"
.text
.globl test
.type test, @function
test:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $2, %esi
movl $1, %edi
call plus
movl %eax, %esi
leaq .LC0(%rip), %rax
movq %rax, %rdi
movl $0, %eax
call printf@PLT
leaq plus(%rip), %rax
movq %rax, %rsi
leaq .LC1(%rip), %rax
movq %rax, %rdi
movl $0, %eax
call printf@PLT
nop
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size test, .-test
.ident "GCC: (GNU) 12.2.0"
.section .note.GNU-stack,"",@progbits
我们可以发现,在main.S
中不存在plus
标号,plus
标号只在plus.S
中存在。
因此,当执行到main.S
的call plus@PLT
指令时,将指令指针寄存器(RIP)压栈,然后跳转到plus.S
的plus
标号处运行。
5.在一个源文件里对plus函数进行声明,并在前面加static,另一个源文件里对plus函数进行定义,在前面也加static
main.c
#include <stdio.h>
static int plus(int a, int b);
void test();
int main() {
printf("%d\n", plus(1, 2));
printf("The address of plus in main.c is %p\n", &plus);
test();
return 0;
}
plus.c
#include <stdio.h>
static int plus(int a, int b) {
return a + b;
}
void test() {
printf("%d\n", plus(1, 2));
printf("The address of plus in plus.c is %p\n", &plus);
}
编译时报错,原因和3类似。
6. 两个源文件里的函数至少有一个加static,且均对plus函数进行定义
main.c
#include <stdio.h>
static int plus(int a, int b) {
return a + b;//或定义成其他实现
}
void test();
int main() {
printf("%d\n", plus(1, 2));
printf("The address of plus in main.c is %p\n", &plus);
test();
return 0;
}
plus.c
#include <stdio.h>
int plus(int a, int b) {//或static int plus(int a, int b)
return a + b;
}
void test() {
printf("%d\n", plus(1, 2));
printf("The address of plus in plus.c is %p\n", &plus);
}
顺利通过编译,输出两个不同的地址。
3
The address of plus in main.c is 0x55f618b8d139
3
The address of plus in plus.c is 0x55f618b8d1a5
参考资料:《C和指针》