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 ...
随机推荐
- 实用的git log用法
git log可以很方便地查看日志,可以根据自己需要,将日志按照特定格式显示,或者输出某种格式. 最原始的输出样式: $ git log commit ca82a6dff817ec66f4434200 ...
- 温故知新——Spring AOP
Spring AOP 面向切面编程,相信大家都不陌生,它和Spring IOC是Spring赖以成名的两个最基础的功能.在咱们平时的工作中,使用IOC的场景比较多,像咱们平时使用的@Controlle ...
- 模拟退火详解&P1433题解
前排提示:LZ是个菜比,有可能有讲的不对的地方,请在评论区指出qwq 0.基本思想 模拟退火其实没有那么高大上.说白了就是初始化一个"温度".每次随机乱选一个方案,如果比以前的方案 ...
- 【java学习笔记】LongAdder
目录 1.背景 2.LongAdder 3.Striped64内部结构 4.LongAdder的add方法解析 5.Striped64的longAccumulate方法解析 6.总结 LongAdde ...
- 目标识别AI资料
朋友推荐的, 还有自己搜的. 入门可以看看. 网上资料应该不少, 一搜一大把, 简单记下地址. Review of Deep Learning Algorithms for Object Detect ...
- Codeforces1247D Power Products 暴力+优化
题意 给定数组\(a(\left| a \right|\leq 10^5)\)和整数\(k(2\leq k \leq 100)\),问满足一下条件的二元组\(<i,j>\)的数目: \(1 ...
- Java 与 Mysql连接,并分页显示
这是我第一个上规模的Java项目,我们必须在一周内完成的作业,零基础学习Java,网上收集了很多资料,逐渐对面向对象的思想有所了解,但还是半灌水,后期打算结合项目系统地学习一遍Java.老师布置的任务 ...
- 用Python写一个随机数字生成代码,5行代码超简单
本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 第一步,安装 random 库 random库是使用随机数的Python标准库 ...
- JsonAnalyzer2 1.01版
本版的改进主要在字符串的处理,前版不允许出现[]{},:等,现在都可以了,做出的修改主要在Lexer类,另外Token类增加1了下标,TreeBuilder类的不合语法处也做出一定修改. 测试用例:h ...
- Traveling by Stagecoach(POJ 2686)
原题如下: Traveling by Stagecoach Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 4494 Ac ...