编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏

19.变量说明符

C 语言允许声明变量的时候,加上一些特定的说明符(specifier),为编译器提供变量行为的额外信息。它的主要作用是帮助编译器优化代码,有时会对程序行为产生影响。

auto

auto说明符表示该变量的存储,由编译器自主分配内存空间,且只存在于定义时所在的作用域,退出作用域时会自动释放。

由于只要不是extern的变量(外部变量),都是由编译器自主分配内存空间的,这属于默认行为,所以该说明符没有实际作用,一般都省略不写。

auto int a;
// 等同于
int a;

extern

extern说明符表示,该变量在其他文件里面声明,没有必要在当前文件里面为它分配空间。通常用来表示,该变量是多个文件共享的。

extern int a;

上面代码中,a是extern变量,表示该变量在其他文件里面定义和初始化,当前文件不必为它分配存储空间。

但是,变量声明时,同时进行初始化,extern就会无效。

// extern 无效
extern int i = 0;

// 等同于
int i = 0;

上面代码中,extern对变量初始化的声明是无效的。这是为了防止多个extern对同一个变量进行多次初始化。

函数内部使用extern声明变量,就相当于该变量是静态存储,每次执行时都要从外部获取它的值。

函数本身默认是extern,即该函数可以被外部文件共享,通常省略extern不写。如果只希望函数在当前文件可用,那就需要在函数前面加上static。

extern int f(int i);
// 等同于
int f(int i);

register

register说明符向编译器表示,该变量是经常使用的,应该提供最快的读取速度,所以应该放进寄存器。但是,编译器可以忽略这个说明符,不一定按照这个指示行事。

register int a;

上面示例中,register提示编译器,变量a会经常用到,要为它提供最快的读取速度。

register只对声明在代码块内部的变量有效。

设为register的变量,不能获取它的地址。

register int a;
int *p = &a; // 编译器报错

上面示例中,&a会报错,因为变量a可能放在寄存器里面,无法获取内存地址。

如果数组设为register,也不能获取整个数组或任一个数组成员的地址。

register int a[] = {11, 22, 33, 44, 55};

int p = a;  // 报错
int a = *(a + 2); // 报错

历史上,CPU 内部的缓存,称为寄存器(register)。与内存相比,寄存器的访问速度快得多,所以使用它们可以提高速度。但是它们不在内存之中,所以没有内存地址,这就是为什么不能获取指向它们的指针地址。现代编译器已经有巨大的进步,会尽可能优化代码,按照自己的规则决定怎么利用好寄存器,取得最佳的执行速度,所以可能会忽视代码里面的register说明符,不保证一定会把这些变量放到寄存器。

volatile

volatile说明符表示所声明的变量,可能会预想不到地发生变化(即其他程序可能会更改它的值),不受当前程序控制,因此编译器不要对这类变量进行优化,每次使用时都应该查询一下它的值。硬件设备的编程中,这个说明符很常用。

volatile int foo;
volatile int* bar;

volatile的目的是阻止编译器对变量行为进行优化,请看下面的例子。

int foo = x;
// 其他语句,假设没有改变 x 的值
int bar = x;

上面代码中,由于变量foo和bar都等于x,而且x的值也没有发生变化,所以编译器可能会把x放入缓存,直接从缓存读取值(而不是从 x 的原始内存位置读取),然后对foo和bar进行赋值。如果x被设定为volatile,编译器就不会把它放入缓存,每次都从原始位置去取x的值,因为在两次读取之间,其他程序可能会改变x。

restrict

restrict说明符允许编译器优化某些代码。它只能用于指针,表明该指针是访问数据的唯一方式。

int* restrict pt = (int*) malloc(10 * sizeof(int));

上面示例中,restrict表示变量pt是访问 malloc 所分配内存的唯一方式。

下面例子的变量foo,就不能使用restrict修饰符。

int foo[10];
int* bar = foo;

上面示例中,变量foo指向的内存,可以用foo访问,也可以用bar访问,因此就不能将foo设为 restrict。

如果编译器知道某块内存只能用一个方式访问,可能可以更好地优化代码,因为不用担心其他地方会修改值。

restrict用于函数参数时,表示参数的内存地址之间没有重叠。

void swap(int* restrict a, int* restrict b) {
  int t;
  t = *a;
  *a = *b;
  *b = t;
}

上面示例中,函数参数声明里的restrict表示,参数a和参数b的内存地址没有重叠。

贡献者: yangchong211