C 类型限定符
C 类型限定符
1. Introduction
C 语言中的大部分类型都可以用称为限定符(qualifier)的关键字 const、 volatile、 restrict、 _Atomic 加以限定。这些限定符可以单独使用,也可以组合使用。
const 和 volatile 在 C89/C90 版本定义,restrict 在 C99 版本定义,_Atomic 在 C11 版本定义,_Atomic 对于编译器而言是可选的支持特性,表示原子类型。
一个类型不加限定符的版本和加限定符的版本是两个不同的类型,但是有相同的表示方法和对齐要求。
因此,int 是一个类型,const int 是另一个类型,限定的结果是产生一个新的类型,而不是“仅仅加入一个修饰,使其具有只读属性”。
程序员可以通过限定符提供一定的信息,以便 C 实现可以更好地优化程序。当编译器编译程序时,看到这些限定符就知道哪些优化是可以做的,哪些优化是不可以做的。另外,还可以检查出程序中的不当操作
类型限定符有以下的使用规则:
a. 类型限定符和类型指定符的顺序可交换
const int i;
int const i;
上述两种表示方式是等价的,但是处于可读性的考虑,通常采用第一种表示方式。
b. 派生类型不会继承类型限定符
指针派生自它所指向的类型。类型 const int 可以派生出 const int *,该指针的类型是“指向一个 const int 类型的指针”,而不是具有 const 限定的指针。
如果要生成 const 指针,需要对指针单独限定:
int * const cp; // cp 是 const 指针,派生自无限定的 int 类型
const int * const cpc; // cpc 是 const 指针,派生自 const int 类型
struct 类型派生自它的成员类型,如下所示:
struct t {const int i; const float f;}
struct t 派生自两个成员类型 const int, const float,但是 struct t 不是 const 限定的类型。
c. 同一个类型限定符同时出现多次,相当于只出现一次
两个声明指定符连用是非法的,但是两个类型限定符连用是合法的:
int int i; // 非法
const volatile const const int i; // 等效于 const volatile int i
int function(const const int i); // 等效于 int function(const int i);
2. const
const 比较准确的含义是"只读",而不是“常量”,“常量”在 C 语言中有特定的含义,与 const 无关。
const 有以下几点需要了解:
a. 优化
当程序中出现 const int i = 0 这样的代码时,它表示程序不准备修改对象 i 的存储值,编译器可以根据这种意图提供适当的优化。
比如,编译器可以在第一次访问对象 i 时将它缓存起来,以后只需要使用这个缓存值而不需要浪费时间重新读取。
b. 安全
编译器会检查代码是否违反了 const 的限定规则,对变量做了不适当的修改操作。
但是用 const 限定的对象并非不可改写的,可以通过强制类型转换来绕过这种限制。
例如:
int x = 0;
const int *p = &x;
(*p)++; //S1
(*(int*)p)++; //S2
S1 是非法的,S2 是合法的,S2 的做法是非常危险的,其后果未定义。
c.作用范围
const 限定符有其作用范围,如果多段代码共享同一个对象,或者多个线程共享同一个对象,那么这个对象可能在一个范围内是 const 限定的,在另一个范围内则不是。
例如:
void f(const char* c)
{
*c = 'x'; //S1,非法
}
void g(void)
{
char a[3];
f(a);
}
上述数组 a 的元素在 g() 中是可修改的,在 f() 中是只读的, S1 代码段的操作是非法的。
3. volatile
与 const 相反,volatile 的含义是告诉 C 实现,对象的值会改变,并且是以不受控制的方式改变。
如果一个类型是 “volatile 限定的类型”,则意味着该类型所定义的对象,它的值不单单会被当前程序的代码修改,还可能潜在地被其他程序或代码修改。
因此,编译器不能在编译时对访问该对象的代码做优化处理,对这种对象的处理不能依赖于缓存特性。
例如,它可能对应一个硬件的端口,或者几个程序或线程公用的存储位置等等。
int i;
interrupt_handler()
{
i = 1;
}
void function()
{
i = 0;
while (0 == i);
}
虽然本意是在中断服务函数中将 i 赋值为 1,但实际上 function 函数中的 while 循环可能永远不会退出。
因为编译器看到在 while 循环前 i 被赋值为 0,while(0 == i) 的条件比较结果肯定为 1,如果打开了编译器的优化选项,while(0 == i) 会被优化为 while(1)。
解决方案就是在声明 i 的时候加入 volatile 限定符:volatile int i;
这样编译器就不会做出不适当的优化。
4. restrict
restrict 限定符仅适用于指针类型。
如果一个指针类型是 restrict 限定的,则它所指向的对象和该指针有一种特殊的联系:在一个代码块内(函数体或者复合语句),所有到这个对象的引用必须直接或者间接通过这个指针进行。
基于上述保证的条件,编译器就可以在代码块的开始处安全地缓存“该指针所指向的对象”的值,读取和更新操作只针对这个缓存值进行。在退出代码块之前,再将缓存的值写回到指针所指向的对象。
如果没有这个限定符,则意味着在当前块内,还可能存在着其他指向这个对象的指针,因此,缓存对象的值是不安全的。
以下两个代码段:
void f1(int *p1, int *p2, int n)
{
for (int i = 0; i < n; i++) {
*p1 ++;
*p2 ++
}
}
void f2(int * restrict p1, int * restrict p2, int n)
{
for (int i = 0; i < n; i++) {
*p1 ++;
*p2 ++
}
}
用相同的方法调用上述 f1 和 f2:
int i = 0;
f1(&i, &i, 10)
int i = 0;
f2(&i, &i, 10)
f1 调用完成后,i 的值是 90;f2 调用完成后,i 的值是 45
原因在于,在 f2 中,参数 p1 和 p2 都被声明为 restrict 限定,但调用时他们又指向了同一个对象,这违背了 restrict 的约定。
因此在 f2 内部,for 循环里的两个累加过程都认为只有自己在改变对象的值,因此都使用了缓存值,而不是实际访问对象的值。
当退出 for 循环时他们各自的值都是 45,退出 f2 函数前,这两个缓存值被各自刷新到 i 中,导致 i 的值是 45。
C 类型限定符的更多相关文章
- CUDA1.1-函数类型限定符与变量类型限定符
这部分来自于<CUDA_C_Programming_Guide.pdf>,看完<GPU高性能变成CUDA实战>的第四章,觉得这本书还是很好的,是一种循序渐进式的书,值得看,而不 ...
- GPU编程自学6 —— 函数与变量类型限定符
深度学习的兴起,使得多线程以及GPU编程逐渐成为算法工程师无法规避的问题.这里主要记录自己的GPU自学历程. 目录 <GPU编程自学1 -- 引言> <GPU编程自学2 -- CUD ...
- C语言中类型限定符
通常用类型和存储类别来描述一个变量. C90还增加了两个属性:恒常性(constancy).易变性(volatility): 分别用关键字const和volatile来声明. 这两个关键字创建的类型是 ...
- ISO/IEC 9899:2011 条款6.7.3——类型限定符
6.7.3 类型限定符 语法 1.type-qualifier: const restrict volatile _Atomic 约束 2.除了指针类型(其被引用的类型是一个对象类型)之外的类型,不应 ...
- 类型限定符volatile
目录 类型限定符volatile 强制内存读取 禁止编译优化 注意:volatile不能够保证线程同步 volatile bool flag; volatile int a; 添加volatile限定 ...
- C:类型限定符
- 解决 “MoveFile”: 类型库“XXX.dll”中的标识符已经是宏;使用“rename”限定符 类型库符号与系统符号冲突问题
今天在VS工程当中引入一个组件,编译的时候出现警告, “MoveFile”: 类型库“XXX.dll”中的标识符已经是宏:使用“rename”限定符.虽然只是一个警告,但看着实在不爽,更重要的是,警告 ...
- 变量和基本类型——复合类型,const限定符,处理类型
一.复合类型 复合类型是指基于其他类型定义的类型.C++语言有几种复合类型,包括引用和指针. 1.引用 引用并非对象,它只是为一个已存在的对象所起的另外一个名字. 除了以下2种情况,其他所有引用的类型 ...
- ERROR C3848:具有类型"const XXX" 的表达式会丢失一些 const-volatile 限定符以调用"YYY" with"ZZZ"
今天看书,Thinking in c++ volume 2 "Adaptable function objects" 里面作者说: Suppose, for example, th ...
随机推荐
- [NOI2020]美食家 题解
题意分析 给出一个带权有向图,要求从节点 $1$ 出发,经过恰好 $T$ 的边权和,回到节点 $1$ ,求可经过的最大点权和.特别地,经过的边权和达到部分特殊数时,会有某个点的点权发生改变. 思路分析 ...
- 每天定时下载gfs资料shell脚本
在数值天气预报应用中,经常需要下载一些输入资料,美国ncep的gfs资料是常用的一种分析场资料.业务运行,需要每天定时从ncep网站上下载,所以写了一个Shell脚本实现这一功能.脚本内容如下: #! ...
- go语言基础实例
一:变量 1)变量声明 func main() { var v1 int var v2 int //一次声明多个变量 var ( v3 int v4 int ) //变量最开始会有一个零值 fmt.P ...
- composer版本号前面`^`和`~`的区别
~1.2.3表示: 1.2.3 <= 版本号 < 1.3.0 ^1.2.3表示: 1.2.3 <= 版本号 < 2.0.0 ~1.2 表示: 1.2.0 <= 版本号 & ...
- 前端修仙之路---一、如何用gulp搭建一套web前端开发框架
引言 相信从事web前端开发的朋友都知道,现在流行的Vue.AngularJS等框架中,它们都有独立的脚手架来创建项目,比如Vue有vue-cli,Angular有angula-cli.脚手架可以一键 ...
- vue 里bus的使用
兄弟组件之间进行传值(非父子组件): 安装:npm install vue-bus 在main.js中引入vue-bus import Vue from 'vue'; import VueBus fr ...
- JAVA,.NET项目开发难上手?力软敏捷开发框架解君愁
力软敏捷开发框架/快速开发平台是一款轻量化多语言可视化开发工具.秉持以“让开发变得简单”为宗旨,深耕软件平台, 拥有近10年的行业开发经验,经典的.NET软件产品已经服务超5000家客户,并得 ...
- ctf古典密码从0到
本文首发于“合天智汇”公众号 作者:淡灬看夏丶恋雨 古典密码和现代密码的区别: 代换密码 单表代换密码 字符或数学型 凯撒密码 仿射密码 四方密码 培根密码 图表 标准银河字母 圣堂武士密码 猪圈密码 ...
- js 基础面试
1.['1', '2', '3'].map(parseInt) let arr = ['1', '2', '3'] let res1 = arr.map(Number) // [ 1, 2, 3 ] ...
- 15_Python的模块module
1.模块的概述 1.模块是Python程序架构的一个核心概念,每一个以.py结尾的Python源代码文件都是一个模块 2.模块名和标识符的命名规则一样,由数字字母下划线组成且不能以数字开头,也不要和系 ...