C语言 extern 和 static

摘抄AI的,豆包,置顶了。
如果main.c 有个全局变量uint8_t on_set = 9;
other.c 想根据 on_set 的值进行业务操作。
怎么操作

第一种,使用 extern  
//main.c
#include <stdint.h>
uint8_t on_set = 9;  //【变量定义】分配内存+生成全局符号on_set

//other.c
#include <stdint.h>
extern uint8_t on_set; //【变量声明】只告诉编译器类型,不定义、不开内存
void test(void){
    if(on_set ==9){  //业务代码 }
}


阶段 1:预处理 (-E)
main.c: uint8_t on_set =9;
other.c:extern uint8_t on_set; void test(){if(on_set==9){}}
extern是编译期关键字,预处理直接保留,不被优化。

阶段 2:编译 (-S) → 生成汇编.s

main.s汇编:
    .globl on_set       # .globl:把on_set标记为【全局符号】,可供其他.o链接查找
    .data               # 放到全局数据段(已初始化全局变量区)
on_set:
    .byte 9             # 在.data段分配1字节内存,初始值9


other.s 汇编:
test:
    movzbl on_set(%rip),%eax  # 引用符号on_set,但**不知道on_set地址**
    cmpb $9,%al
    ret


阶段 3:汇编 (-c) → 生成 main.o、other.o(ELF 目标文件)
用nm查看符号表:
nm main.o
0000000000000000 D on_set   # D=Data段:已定义、占用内存,全局符号
nm other.o
                 U on_set   # U=Undefined:未定义符号,需要链接时去别的.o匹配
0000000000000000 T test    # T=Text代码段,函数符号

阶段 4:链接 ld(gcc 最后一步链接)
链接器核心工作:符号重定位
遍历所有.o 符号表,发现other.o(U on_set)需要绑定main.o(D on_set)的实际地址;
把 other.s 里on_set(%rip)的占位符号,替换成 main.o 中 on_set 在进程数据段的真实偏移地址;
最终整个程序只有 1 份 on_set 内存,两个文件读写同一块地址。
易错点:
如果两个.c 都写uint8_t on_set=9;,两个.o 都有D on_set,链接器报多重定义 multiple definition。


方式 2:封装 get/set 函数
common.h
#ifndef COMMON_H
#define COMMON_H
#include <stdint.h>
uint8_t get_on_set(void);
void set_on_set(uint8_t val);
#endif


#include "common.h"
static uint8_t on_set = 9; // static限定只本文件可见
uint8_t get_on_set(void)
{
    return on_set;
}
void set_on_set(uint8_t val)
{
    on_set = val;
}

#include "common.h"

void func(void)
{
    uint8_t val = get_on_set();
    if(val ==9)
    {
        //业务
    }
}

阶段 2:编译 (-S) → 生成汇编.s

main.c -> main.s
    .local on_set    # .local:局部符号,不对外导出(static实现原理)
    .data
on_set:
    .byte 9
    .globl get_on_set # 函数加.globl,导出全局符号
get_on_set:
    movb on_set(%rip),%al
    ret

static对全局变量作用:取消.globl,符号变成本地.local,链接器在别的.o 看不见 on_set。

other.s 关键:
test:
    call get_on_set  # 调用未解析函数符号get_on_set
    cmpb $9,%al
    ret


other.s 关键:
test:
    call get_on_set  # 调用未解析函数符号get_on_set
    cmpb $9,%al
    ret

other.c 完全不知道on_set存在,代码只依赖get_on_set函数符号。

阶段 3:汇编成.o,nm 查看符号
nm main.o
0000000000000000 d on_set  # 小写d:本地私有符号,外部不可见
0000000000000000 T get_on_set # 大写T:全局导出函数
nm other.o
                 U get_on_set # 未定义函数符号
on_set被 static 隐藏,other.o 从符号表里根本找不到这个变量。

阶段 4:链接
链接器匹配:other.oU get_on_set ↔ main.oT get_on_set,填充函数真实地址;
other 想要读 on_set → 跳转执行 main 里的 get_on_set 函数,由函数内部去访问私有 on_set;
内存:仍然只有 1 份 on_set,但外部无法直接通过变量名篡改,所有读写必须经过接口函数。

补充:
C 规则:全局函数原型,默认隐式 extern,头文件不用手动加 extern。
规则 1:函数分「声明」和「定义」
函数声明(原型):只有uint8_t get_on_set(void); 分号结尾,无 {}
作用:告知编译器函数名字、入参、返回值;
不生成任何汇编代码,不占用程序空间;
头文件专属。
函数定义(实现):uint8_t get_on_set(void){xxx} 带大括号
生成汇编指令、代码;
一个工程只能定义 1 次,放在某个.c;
其余所有文件只用声明。

规则 2:extern 对函数的作用
uint8_t get_on_set(void); ≡ extern uint8_t get_on_set(void);
C 语言全局函数默认 extern 修饰,写不写 extern 完全一样。
变量不一样:变量不加 extern 就是定义,会分配内存。

补充:extern 在汇编里做了啥?
other.c 里extern uint8_t on_set;
编译阶段不产生任何 .data、不定义符号;
汇编里直接使用符号on_set,符号标记 U (Undefined);
链接器去别的.o 找带.globl on_set的符号做地址回填。


posted @ 2026-06-06 14:39  一见无始道成空  阅读(9)  评论(0)    收藏  举报