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 类型限定符的更多相关文章

  1. CUDA1.1-函数类型限定符与变量类型限定符

    这部分来自于<CUDA_C_Programming_Guide.pdf>,看完<GPU高性能变成CUDA实战>的第四章,觉得这本书还是很好的,是一种循序渐进式的书,值得看,而不 ...

  2. GPU编程自学6 —— 函数与变量类型限定符

    深度学习的兴起,使得多线程以及GPU编程逐渐成为算法工程师无法规避的问题.这里主要记录自己的GPU自学历程. 目录 <GPU编程自学1 -- 引言> <GPU编程自学2 -- CUD ...

  3. C语言中类型限定符

    通常用类型和存储类别来描述一个变量. C90还增加了两个属性:恒常性(constancy).易变性(volatility): 分别用关键字const和volatile来声明. 这两个关键字创建的类型是 ...

  4. ISO/IEC 9899:2011 条款6.7.3——类型限定符

    6.7.3 类型限定符 语法 1.type-qualifier: const restrict volatile _Atomic 约束 2.除了指针类型(其被引用的类型是一个对象类型)之外的类型,不应 ...

  5. 类型限定符volatile

    目录 类型限定符volatile 强制内存读取 禁止编译优化 注意:volatile不能够保证线程同步 volatile bool flag; volatile int a; 添加volatile限定 ...

  6. C:类型限定符

  7. 解决 “MoveFile”: 类型库“XXX.dll”中的标识符已经是宏;使用“rename”限定符 类型库符号与系统符号冲突问题

    今天在VS工程当中引入一个组件,编译的时候出现警告, “MoveFile”: 类型库“XXX.dll”中的标识符已经是宏:使用“rename”限定符.虽然只是一个警告,但看着实在不爽,更重要的是,警告 ...

  8. 变量和基本类型——复合类型,const限定符,处理类型

    一.复合类型 复合类型是指基于其他类型定义的类型.C++语言有几种复合类型,包括引用和指针. 1.引用 引用并非对象,它只是为一个已存在的对象所起的另外一个名字. 除了以下2种情况,其他所有引用的类型 ...

  9. ERROR C3848:具有类型"const XXX" 的表达式会丢失一些 const-volatile 限定符以调用"YYY" with"ZZZ"

    今天看书,Thinking in c++ volume 2 "Adaptable function objects" 里面作者说: Suppose, for example, th ...

随机推荐

  1. 实用的git log用法

    git log可以很方便地查看日志,可以根据自己需要,将日志按照特定格式显示,或者输出某种格式. 最原始的输出样式: $ git log commit ca82a6dff817ec66f4434200 ...

  2. 温故知新——Spring AOP

    Spring AOP 面向切面编程,相信大家都不陌生,它和Spring IOC是Spring赖以成名的两个最基础的功能.在咱们平时的工作中,使用IOC的场景比较多,像咱们平时使用的@Controlle ...

  3. 模拟退火详解&P1433题解

    前排提示:LZ是个菜比,有可能有讲的不对的地方,请在评论区指出qwq 0.基本思想 模拟退火其实没有那么高大上.说白了就是初始化一个"温度".每次随机乱选一个方案,如果比以前的方案 ...

  4. 【java学习笔记】LongAdder

    目录 1.背景 2.LongAdder 3.Striped64内部结构 4.LongAdder的add方法解析 5.Striped64的longAccumulate方法解析 6.总结 LongAdde ...

  5. 目标识别AI资料

    朋友推荐的, 还有自己搜的. 入门可以看看. 网上资料应该不少, 一搜一大把, 简单记下地址. Review of Deep Learning Algorithms for Object Detect ...

  6. Codeforces1247D Power Products 暴力+优化

    题意 给定数组\(a(\left| a \right|\leq 10^5)\)和整数\(k(2\leq k \leq 100)\),问满足一下条件的二元组\(<i,j>\)的数目: \(1 ...

  7. Java 与 Mysql连接,并分页显示

    这是我第一个上规模的Java项目,我们必须在一周内完成的作业,零基础学习Java,网上收集了很多资料,逐渐对面向对象的思想有所了解,但还是半灌水,后期打算结合项目系统地学习一遍Java.老师布置的任务 ...

  8. 用Python写一个随机数字生成代码,5行代码超简单

    本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 第一步,安装 random 库 random库是使用随机数的Python标准库 ...

  9. JsonAnalyzer2 1.01版

    本版的改进主要在字符串的处理,前版不允许出现[]{},:等,现在都可以了,做出的修改主要在Lexer类,另外Token类增加1了下标,TreeBuilder类的不合语法处也做出一定修改. 测试用例:h ...

  10. Traveling by Stagecoach(POJ 2686)

    原题如下: Traveling by Stagecoach Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 4494   Ac ...