C语言 static、extern 用法解析

发布于:2022-10-12 ⋅ 阅读:(279) ⋅ 点赞:(0)

static用于不同的上下文环境中时,static关键字具有不同的意思。
本文对C语言中的staticextern关键字做了总结。

关于变量

变量类型 声明的位置 是否在栈中 作用域 如果声明为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前加externtest.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前加externtest.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.cplus函数只有声明没有定义,且plus.cplus函数加了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.Scall plus@PLT指令时,将指令指针寄存器(RIP)压栈,然后跳转到plus.Splus标号处运行。

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和指针》


网站公告

今日签到

点亮在社区的每一天
去签到