飞翔飞翔
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
  • 学习路径
  • 第1章 编程基础概念

    • 冯·诺依曼体系结构
    • 数据在计算机中的表示
    • 编程语言的层次
    • C语言的起源与发展
    • C99标准的主要改进
    • 开发环境搭建
    • 第一个C程序
    • 编译与运行流程
    • 可移植性风险的三级体系
  • 第2章 数据类型与运算

    • 字符集与标识符
    • 关键字
    • 注释
    • char 类型
    • short 与 int
    • long 与 long long
    • 有符号与无符号
    • 取值范围与 limits.h
    • float 与 double
    • long double
    • _Bool 类型
    • 变量声明与定义
    • 常量
    • 转义序列
    • 算术运算符
    • 赋值运算符
    • 自增自减运算符
    • 关系与判等运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 逗号运算符
    • 运算符优先级
    • 隐式类型转换
    • 显式类型转换
  • 第3章 控制流

    • 表达式语句与空语句
    • 复合语句
    • if 语句
    • switch 语句
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
    • goto 语句
    • return 语句
  • 第4章 函数与模块化编程

    • 函数定义
    • 函数声明与原型
    • main 函数
    • 函数调用机制
    • 传值调用
    • 数组参数
    • 作用域
    • 存储期
    • 链接属性
    • static 与 extern
    • 递归
    • 头文件与源文件
    • 头文件保护
    • include 规则
  • 第5章 数组与字符串

    • 一维数组声明与初始化
    • 数组的存储模型
    • 数组访问与越界
    • 数组操作
    • 二维数组
    • 变长数组 VLA
    • 字符串基础
    • 字符串输入输出
    • 字符串处理函数
    • 字符串与数字转换
  • 第6章 指针

    • 指针的概念
    • 指针的声明与使用
    • 指针运算
    • const 与指针
    • 数组名与指针
    • 指针遍历数组
    • 指针与多维数组
    • 指针作为函数参数
    • 函数返回指针
    • 函数指针
    • 二级指针
    • 复杂声明解析
  • 第7章 结构体、联合体与枚举

    • 结构体定义与声明
    • 结构体初始化
    • 结构体成员访问
    • 结构体嵌套
    • 结构体指针
    • 结构体与函数
    • 联合体
    • 联合体与类型双关
    • 枚举类型
    • 位域
    • 内存对齐与填充
  • 第8章 动态内存管理

    • malloc 与 free
    • calloc 与 realloc
    • 内存泄漏
    • 悬垂指针
    • 内存分配策略
    • 自定义内存池
    • Valgrind 与内存检测
    • 内存碎片
    • 内存对齐分配
    • 常见内存错误
  • 第9章 文件输入输出

    • 文件打开与关闭
    • 文本读写
    • 格式化输入输出
    • 二进制读写
    • 文件定位
    • 错误处理
    • 标准流
    • 临时文件
    • 文件操作示例
  • 第10章 预处理器

    • 预处理器基础
    • 宏定义
    • 带参数的宏
    • 条件编译
    • 头文件包含
    • 预定义宏
    • 宏的高级技巧
    • 预处理器陷阱
    • 编译器特定扩展
  • 第11章 标准库概览

    • 标准库概述
    • assert.h
    • ctype.h
    • errno.h
    • float.h
    • limits.h
    • locale.h
    • math.h
    • setjmp.h
    • signal.h
    • stdarg.h
    • stddef.h
    • stdlib.h
  • 第12章 进阶主题

    • 内联函数
    • 变长数组 VLA
    • 复数类型
    • 布尔类型
    • stdint 与 inttypes
    • 灵活数组成员
    • 匿名结构体与联合体
    • 静态断言
    • 线程支持
    • 原子操作

链接属性

链接属性(Linkage)决定了标识符在不同翻译单元(源文件)之间是否可见。C 语言有三种链接属性:外部链接、内部链接和无链接。理解链接属性,是组织多文件项目、避免命名冲突的基础。

三种链接属性

外部链接(External Linkage)

具有外部链接的标识符可以在多个源文件中访问。全局变量和函数默认具有外部链接:

/* file1.c */
int global_count = 0;       /* 外部链接 */

void increment(void)          /* 外部链接 */
{
    global_count++;
}

/* file2.c */
extern int global_count;      /* 声明外部变量 */
extern void increment(void);  /* 声明外部函数 */

void print_count(void)
{
    printf("%d\n", global_count);
}

内部链接(Internal Linkage)

具有内部链接的标识符只在当前翻译单元(源文件)内可见。用 static 修饰的全局变量和函数具有内部链接:

/* file1.c */
static int internal_count = 0;    /* 内部链接:只在 file1.c 可见 */

static void helper(void)          /* 内部链接:只在 file1.c 可见 */
{
    /* ... */
}

/* file2.c */
extern int internal_count;        /* 错误:internal_count 是内部链接 */
extern void helper(void);         /* 错误:helper 是内部链接 */

无链接(No Linkage)

局部变量、typedef 名、枚举常量等没有链接属性,只在声明它们的作用域内可见:

void func(void)
{
    int local = 10;         /* 无链接 */
    typedef int Integer;    /* 无链接 */
    enum { RED, GREEN };    /* 枚举常量无链接 */
}

链接属性总结

声明链接属性
全局变量(非 static)外部链接
全局变量(static)内部链接
函数(非 static)外部链接
函数(static)内部链接
局部变量无链接
typedef无链接
枚举常量无链接
结构体/联合体/枚举标记特殊(见下文)

标记的链接属性

结构体、联合体和枚举的标记(tag)有独立的链接属性规则:

struct Point { int x, y; };     /* 标记 Point 的链接属性与作用域相关 */

/* 如果声明在文件作用域,标记具有外部链接 */
/* 如果声明在块作用域,标记无链接 */

标记的链接属性不影响类型本身的使用——只要类型定义可见,就可以使用。

extern 声明

extern 用于声明在其他地方定义的变量或函数:

/* 声明外部变量(不分配内存) */
extern int global_count;

/* 声明外部函数 */
extern void increment(void);

extern 声明不创建变量,只是告诉编译器"这个变量在其他地方定义"。如果 extern 声明有初始化器,它就变成了定义:

extern int x = 10;          /* 有初始化器,这是定义,不是声明 */

重复声明

具有外部链接的标识符可以在多个文件中声明,但只能在一个文件中定义:

/* file1.c */
int shared = 10;            /* 定义 */

/* file2.c */
extern int shared;          /* 声明 */

/* file3.c */
extern int shared;          /* 声明 */

如果多个文件都定义了同名外部变量(没有 extern),链接器会报错"multiple definition"。

static 的链接属性作用

static 在全局上下文中改变链接属性,不改变存储期或作用域:

/* 外部链接 → 内部链接 */
static int private_var = 0;     /* 只在当前文件可见 */

static void private_func(void)  /* 只在当前文件可见 */
{
    /* ... */
}

这是信息隐藏(Information Hiding)的基本机制——将实现细节限制在文件内部,只暴露必要的接口。

常见错误

重复定义:

/* file1.c */
int count = 0;              /* 定义 */

/* file2.c */
int count = 0;              /* 错误:重复定义 */

/* 正确 */
extern int count;           /* 声明 */

头文件中定义变量:

/* utils.h */
int shared_count = 0;       /* 危险:每个包含此头文件的 .c 文件都有定义 */

/* 正确 */
extern int shared_count;    /* 头文件中只声明 */

/* utils.c */
int shared_count = 0;       /* 在一个 .c 文件中定义 */

混淆 static 的两种含义:

static int global = 0;      /* 文件作用域:static 改变链接属性为内部 */

void func(void)
{
    static int local = 0;   /* 块作用域:static 改变存储期为静态 */
}

最佳实践

  • 全局变量默认用 static 限制为内部链接
  • 需要跨文件访问的变量和函数,在一个 .c 文件中定义,在头文件中用 extern 声明
  • 不要在头文件中定义变量(只声明)
  • 用 static 函数隐藏内部实现
  • 理解 static 的双重含义(链接属性 vs 存储期)
上一页
存储期
下一页
static 与 extern