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 ...
随机推荐
- asp.net Core3.1自定义权限体系-菜单和操作按钮权限
我们在做项目项目,经常会碰到权限体系,权限体系属于系统架构的一个最底层的功能,也是非常重要的功能,几乎在每个项目都会用到.那么我们该如何设计一个比较合理的且扩展性较强的权限体系呢? 经过多天的摸索,参 ...
- sql 游标(理论)
游标是处理结果集的一种机制 --声明游标 --ISO 语法 DECLARE cursor_name [ INSENSITIVE ] [ SCROLL ] CURSOR FOR select_state ...
- 百度与谷歌seo优化的差别
http://www.wocaoseo.com/thread-126-1-1.html 常有朋友问谷歌(google)和百度(baidu)到底有什么区别?我在纠结这个问题该如何回答.如果从公平公正的角 ...
- 焦大:做SEO应该研究的用户需求的方向
http://www.wocaoseo.com/thread-60-1-1.html 最近收到打击很大,收获也颇多,这一切都莫过于用户需求的问题.我曾经给我弟说过,我对检索排名特征识别.提取和计算自认 ...
- JavaScript函数的高级用法
1.函数的定义和调用 1.1函数的定义方式 方式1 函数声明方式 function 关键字 (命名函数) function fn(){} 方式2 函数表达式(匿名函数) var fn = functi ...
- 认证授权:学习OIDC
前言 上一篇文章介绍了OAuth2.0协议的相关内容,知道OAuth2.0是一个授权协议,无法提供完善的身份认证功能.那么什么来解决身份认证功能呢?——OIDC是一个不错的解决方案.接下来进一步来了解 ...
- 好看的css渐变颜色大全网址
60个渐变颜色 https://webkul.github.io/coolhue/ 60个非常有用的CSS代码片段 https://baijiahao.baidu.com/s?id=160278735 ...
- C# 转化成 json ,特殊字符的处理
//1.定义string jsonText0= "{\"beijing\":{\"zkkke\":\"2222\",\" ...
- C003:计算球体体积 自行输入球体半径
程序: #include "stdafx.h" int _tmain(int argc, _TCHAR* argv[]) { float sphereRadius; do{ pri ...
- [转载] 微软发布 SURFACE DUO ANDROID SDK 和模拟器
模拟器截图 微软今天发布了双屏折叠设备 Surface Duo Android 开发工具(SDK 和模拟器),Windows 10X 开发工具和模拟器之后 2 月 11 日发布,并宣布了新的针对双屏体 ...