ブロガーMindtechnistをフォローするか、[ Intelligent Technology Community ] に参加して、Linux、C、C++、Python、Matlab、ロボット モーション コントロール、マルチロボット コラボレーション、インテリジェントな最適化アルゴリズム、フィルター推定、マルチセンサー情報融合、マシンを学び、共有することを歓迎します。学習、人工知能 知能など関連分野の知識と技術。
C言語標準で定義されている32個のキーワード
コラム「C言語が得意になる」
1. データ型キーワード (12)
C言語のデータ型には主に以下の型があります。実際、データ型は固定サイズのメモリ ブロックのエイリアスとして理解できます。変数に型を割り当てるということは、その変数に割り当てるメモリ領域の量をコンパイラに指示することであり、変数はハウス番号に相当します。メモリブロックの。
(1) 宣言と定義の違い
定義は宣言の特殊なケースとして見なすことができ、すべての宣言が定義であるわけではありません。定義と宣言はメモリを割り当てるかどうかによって区別でき、定義は記憶領域を作成し、宣言は記憶領域を作成しません。
int function()
{
//定义
int val; //定义一个变量val,此时会给val分配内存,由数据类型int决定分配多大内存,int为4字节。
val = 10; //可以为val赋值。
//声明
extern int val_2; //声明变量val_2,不会建立内存。
//val_2 = 10; //error: 声明不会建立内存,没有内存空间所以无法赋值。
return 0;
}
- 定義: 定義とは、オブジェクトを作成し、このオブジェクトにメモリ ブロックを割り当て、同時に変数名をこのメモリ ブロックにバインドすることを指します。ただし、同じ変数は同じスコープ内で 1 回しか定義できず、複数回定義するとコンパイラにより再定義エラーが発生します。
- 声明:
- 特定の名前が予約されており、他のオブジェクト/メモリ ブロックがこの名前を使用できないことをコンパイラーに伝えます。
- 特定の名前がメモリ ブロックにバインドされており、オブジェクトは別の場所で定義されているため、ここでこの名前を使用してもエラーを報告しないことをコンパイラに伝えます。
(2) データ型キーワード
char: 文字変数を宣言します。
char 型は、単一の文字、つまり 1 バイトの記憶単位を格納するために使用されます。char型変数に値を代入する場合は、値を英語の半角シングルクォーテーション「''」で囲む必要があります。 格納する際、実際に文字を格納領域に入れるのではなく、その文字に対応するASCIIコードが格納されます。ストレージユニットに格納されます。(char型は1バイト整数とみなすこともできます)。
ASCII 比較表は次のとおりです。
ASCII値 | 制御文字 | ASCII値 | キャラクター | ASCII値 | キャラクター | ASCII値 | キャラクター |
---|---|---|---|---|---|---|---|
0 | ナット | 32 | (空) | 64 | @ | 96 | 、 |
1 | ソー | 33 | ! | 65 | あ | 97 | ある |
2 | STX | 34 | 」 | 66 | B | 98 | b |
3 | 等 | 35 | # | 67 | C | 99 | c |
4 | EOT | 36 | $ | 68 | D | 100 | d |
5 | ENQ | 37 | % | 69 | E | 101 | e |
6 | 確認応答 | 38 | & | 70 | F | 102 | f |
7 | ベル | 39 | 、 | 71 | G | 103 | g |
8 | BS | 40 | ( | 72 | H | 104 | h |
9 | HT | 41 | ) | 73 | 私 | 105 | 私 |
10 | LF | 42 | * | 74 | J | 106 | j |
11 | VT | 43 | + | 75 | K | 107 | k |
12 | FF | 44 | 、 | 76 | L | 108 | 私 |
13 | CR | 45 | - | 77 | M | 109 | メートル |
14 | それで | 46 | 。 | 78 | N | 110 | n |
15 | と | 47 | / | 79 | ○ | 111 | ああ |
16 | DLE | 48 | 0 | 80 | P | 112 | p |
17 | DCI | 49 | 1 | 81 | Q | 113 | q |
18 | DC2 | 50 | 2 | 82 | R | 114 | r |
19 | DC3 | 51 | 3 | 83 | S | 115 | s |
20 | DC4 | 52 | 4 | 84 | T | 116 | t |
21 | 欲しい | 53 | 5 | 85 | U | 117 | あなた |
22 | シン | 54 | 6 | 86 | V | 118 | v |
23 | 結核 | 55 | 7 | 87 | W | 119 | w |
24 | できる | 56 | 8 | 88 | バツ | 120 | バツ |
25 | EM | 57 | 9 | 89 | Y | 121 | y |
26 | サブ | 58 | : | 90 | Z | 122 | z |
27 | ESC | 59 | ; | 91 | [ | 123 | { |
28 | FS | 60 | < | 92 | / | 124 | | |
29 | GS | 61 | = | 93 | ] | 125 | } |
30 | RS | 62 | > | 94 | ^ | 126 | ` |
31 | 私たち | 63 | ? | 95 | _ | 127 | の |
上記の ASCII コード表では、ASCII 値 0 ~ 31 はプリンタなどの周辺機器の制御に使用される非印刷制御文字を表し、32 ~ 126 はキーボード上にある印刷文字であり、127 は del を表します。コマンド。
エスケープ文字
エスケープ文字 | 意味 | ASCII値(10進数) |
---|---|---|
\a | 警報 | 007 |
\b | バックスペース (BS)、現在位置を前の列に移動します。 | 008 |
\f | フォーム フィード (FF)、現在位置を次のページの先頭に移動します | 012 |
\n | 改行 (LF)、現在位置を次の行の先頭に移動します | 010 |
\r | キャリッジ リターン (CR)、現在位置を行頭に移動します。 | 013 |
\t | 水平タブ (HT) (次の TAB 位置にジャンプ) | 009 |
\v | 垂直集計 (VT) | 011 |
\ | バックスラッシュ文字「」を表します | 092 |
' | 一重引用符 (アポストロフィ) 文字を表します | 039 |
」 | 二重引用符文字を表します | 034 |
? | 疑問符を表します | 063 |
\0 | 番号0 | 000 |
\ddd | 8 進エスケープ文字、d 範囲 0 ~ 7 | 3桁の8進数 |
\xhh | 16 進エスケープ文字、h 範囲 0 9、a f、A ~ F | 3桁の16進数 |
int: 整数変数を宣言します。
在C语言标准中并没有明确规定整型数据的长度,整型数据在内存中所占的字节数与操作系统有关系。(一般为4字节)
打印格式 | 含义 |
---|---|
%d | 输出一个有符号的10进制int类型 |
%o | 输出8进制的int类型 |
%x | 输出16进制的int类型,字母以小写输出 |
%X | 输出16进制的int类型,字母以大写写输出 |
%u | 输出一个10进制的无符号数 |
short:声明短整型变量。
长度一般不长于int型数据。(一般为2字节)
long:声明长整型变量。
长度一般不短于int型数据。(Windows为4字节;Linux为4字节(32位),8字节(64位)。)
打印格式 | 含义 |
---|---|
%hd | 输出short类型 |
%d | 输出int类型 |
%l | 输出long类型 |
%ll | 输出long long类型 |
%hu | 输出unsigned short类型 |
%u | 输出unsigned int类型 |
%lu | 输出unsigned long类型 |
%llu | 输出unsigned long long类型 |
float:声明单精度浮点型变量。
浮点型变量也叫做实型变量,用于存储小数数值。float单精度浮点型一般占用4字节存储空间,7位有效数字。
double:声明双精度浮点型变量。
double双精度浮点型精度高于float单精度浮点型,占用8字节存储空间,15-16位有效数字。
浮点型变量存储的是小数,并且浮点型变量的存储单元是有限的,这就导致一个小数有效位以外的数字将被舍去,这样便会出现一些误差。尤其是float单精度浮点型,有时候将一个小数赋值给一个float型变量,然后打印该浮点型变量都会出现和原小数不一致这样的情况。一般使用double双精度可以提升精度,并且在C语言中,一个小数后面不加f则被认为是双精度double类型,只有小数后面加f才表示float类型,比如3.14f。
signed:声明有符号类型变量。
缺省时,编译器默认为signed有符号类型。在计算机中,所有的数据都是以01的二进制形式来存储的,对于有符号数来说如何表示一个数值的正负是一个问题,因此便有了原码、反码和补码。
- 原码:二进制数据的最高位用来作为符号位,1表示负数,0表示正数,剩余位来表示这个数据的数值大小(绝对值),也就是说,负数的原码是在其绝对值原码的基础上将最高位变成0。原码的表示简单易懂,正负数区分方便且易于转换,但是在实际用于计算时却不太方便,当两个正数做减法运算,或者两个异号的数相加时,必须先比较两个数的绝对值大小才能进行减法运算,以便于决定最终结果是正号还是负号。所以,原码表示数据时不便于做加减运算。
- 反码:正数的反码与原码相同;负数的反码是在负数的原码基础上,符号位不变,其它位全部取反。反码的存在一般是为了方便计算补码。
- 补码:对正数来说,原码、反码、补码是完全一致的;对负数来说,补码是在其反码的基础上将整个数加1。在计算机系统中,所有的数值一律用补码的形式来存储。补码的存在主要有这几种意义:
- 统一0的编码。不管是用原码还是反码来表示0,都会有两种表示方式,即正0和负0,但是我们知道,0不区分正负。这就导致同一个数值0出现两种表示方式,而使用补码表示时,对于正0,原码为00000000,反码为00000000,补码为00000000;对于负0,原码为10000000,反码为11111111,补码为11111111+1=00000000,其中最高位(第九位)数字1被舍弃。这样,正0和负0的补码就一样了。
- 便于运算。使用补码进行运算时可以将减法转化为加法,对于任何数的加减运算,都直接使用补码进行加法运算即可,并且可以将符号位和其他位统一处理,当两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位直接舍弃。
unsigned:声明无符号类型变量。
数据类型 | 占用空间 | 取值范围 |
---|---|---|
short | 2字节 | -32768 到 32767 (-2^15 ~ 2^15-1) |
int | 4字节 | -2147483648 到 2147483647 (-2^31 ~ 2^31-1) |
long | 4字节 | -2147483648 到 2147483647 (-2^31 ~ 2^31-1) |
unsigned short | 2字节 | 0 到 65535 (0 ~ 2^16-1) |
unsigned int | 4字节 | 0 到 4294967295 (0 ~ 2^32-1) |
unsigned long | 4字节 | 0 到 4294967295 (0 ~ 2^32-1) |
struct:声明结构体变量。
数组是相同类型数据的集合,而结构体可以把不同的数据组合成一个整体。通过结构体,我们可以把大量的不同类型数据,甚至是函数和其他复合类型数据打包为一个整体。在使用struct关键字时,应区分开结构体类型和结构体变量的区别,声明结构体类型并不会分配内存,只有在定义结构体类型的时候才会分配内存。通常struct关键字会和typedef关键字一块使用,通过别名的方式可以在定义结构体变量时不需要再写struct关键字。
struct st
{
int a;
char b;
}; //声明结构体类型
struct st s_val = {
1, 'a'}; //定义结构体变量,分配内存
//定义结构体变量时不能省略struct关键字
typedef struct st
{
int a;
char b;
}_st; //给结构体类型取别名为_st
_st val = {
1, 'a'}; //可以不写struct
结构体变量所占的存储空间大小是所有结构体成员所占存储空间大小的总和,并且需要考虑内存对齐方式。而且,空结构体(没有任何成员)也是占存储空间的,空结构体占1字节存储空间。
在结构体中可以包含一种称为柔性数组的成员,柔性数组是一个未知大小的数组,它必须是结构体的最后一个成员,并且柔性数组成员的前面必须有一个其他成员。
struct st
{
int val;
int arr[0]; //int arr[];
};
这个0长度的数组成员arr是不占存储空间的,这个结构体的大小为4字节。有了这个0长度数组我们便可以方便的扩展这个结构体的大小了
struct st *p_st = (struct st *)malloc(sizeof(struct st) + 10 * sizeof(int));
如上,我们使用包含0长度数组的结构体类型定义一个结构体指针,并通过malloc在堆上为其分配一块内存,这块内存的大小为44字节,而结构体类型大小只有4字节,但是我们却可以像访问普通数组一样通过p_st[i]来访问这块内存。也就是说,柔性数组并不是结构体类型的成员,但是通过结构体成员却可以访问我们自定义的柔性数组存储空间。
最后,在C++中,struct结构体和class类的区别,struct成员默认是public属性,而class的成员默认是private属性。
同样,在C语言中也可以实现C++面向对象的效果,使用struct结构可以实现封装,而结构体做结构体成员又可以实现C++中的继承,并且,函数指针做结构体成员可是模仿C++类中的方法。
union:声明联合数据类型。
联合union是一个能在同一个存储空间存储不同类型数据的类型,也就是说,union的所有成员共享同一块存储空间,同一存储空间段可以用来存放几种不同类型的成员,但每一时刻只有一种起作用。联合体所占的存储空间长度为占用存储空间最大的成员的长度,所以也叫做共用体。共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖。并且,共用体变量的地址和它的各成员的地址都是同一地址。
一个union变量只分配一个足够大的存储空间能够存储最大长度的成员,而不会给每一个成员都分配内存,这是union与struct最大的区别。union主要用来达到节省空间的目的,和struct一样,在C++中,union的成员默认属性为public。
看下面的例子
typedef union
{
int data;
char buf[2];
}u_t;
int main()
{
u_t* p, u;
memset(&u, 0, sizeof(u));
p = &u;
p->buf[0] = 0x12;
p->buf[1] = 0x34;
printf("%x\n", p->data);
return 0;
}
对union成员的访问也需要考虑大端存储模式和小端存储模式。
enum:声明枚举类型。
通过enum枚举类型可以定义枚举变量,该枚举变量的值只能是枚举类型中列举出来的那些值。
enum 枚举名
{
枚举值表
};
枚举值表中的所有可用值是枚举变量可以使用的值,也成为枚举元素。枚举值是常量,在程序中枚举值不能作为左值(不能给枚举值使用赋值语句赋值)。另外,枚举元素本身由系统定义了一个表示序号的数值从0开始顺序定义为0,1,2 …依次递增,我们也可以显示的给枚举元素赋值。
enum day
{
mom = 1,
tue, //2
wed, //3
fri = 5,
sat, //6
sun //7
};
我们知道使用宏定义#define也可以定义常量,但是宏定义常量和枚举常量是有区别的,#define 宏常量是在预编译阶段进行简单替换,而枚举常量则是在编译的时候确定其值。
void:声明空类型指针(void类型指针可以接受任何类型指针的赋值,无需类型转换),声明函数无返回值或无参数等。
void主要的用途是限制函数的返回值或者函数参数。在C语言中,如果一个函数不加返回值类型限定,那么编译器会默认该函数返回整型值,所以,当一个函数没有返回值的时候,一定要声明为void类型。当函数没有参数时,也应该声明为void。实际上,在C++中函数参数为void表示该函数不接受任何参数,如果调用该函数时添加了参数那么会报错;而C语言中,参数为void的函数可以接受任何类型的参数。为了统一,无论C还是C++,只要函数没有参数,都要显式指明参数为void。
void类型指针可以指向任何类型的内存块,但是使用void类型指针的时候要格外注意。在ANSI标准中,不允许对void类型指针进行加减操作,这是因为指针的步长是由指针的类型决定的。比如
int *p = 0xaa;
p++; //指针类型为int,每次加一移动4字节
这里int类型的指针每次自加一会移动4字节,因为int类型的对象占据的存储空间就是4字节。而void类型的指针在移动时你并不知道它指向的存储空间的大小。但是在GNU标准中是允许对void类型指针进行加减操作的。为了统一,我们可以在对void类型指针进行加减操作时强制类型转换,以此来说明指针移动步长。
void *p;
(int *)p++;
对于函数来说,如果函数的参数可以是任意类型指针,那么可以将函数参数声明为void*类型,比如典型的C语言内存操作函数memset和memcpy函数,内存操作函数所操作的对象是一块内存本身,本就不应该关心这块内存是什么类型,只要我们通过函数参数告诉编译器我们要操作的这块内存的大小就行了,这也是C语言内存操作函数的精髓所在,并且也体现了作为一个内存操作API的统一性。比如
int buf[20];
memset(&buf, 0, 20 * (sizeof(int)));
这句代码的意思是把buf这个数组清0,我们只要把buf这块内存的首地址传给memset函数,并将要清0的这块内存的大小通过参数传入就可以了。
最后,void是一种抽象,可以参考C++中的抽象类来理解。抽象类不能实例化,同样我们也不能去定义一个void类型的变量,因为在定义变量时,编译器要为变量分配内存,而void类型本身就是一种抽象,编译器不知道分配多大内存给这个变量。通常,void类型用于定义一个可以指向任何类型内存块的指针。
2. 控制语句关键字(12个)
if:条件语句。
else:条件语句中的否定分支,在if后使用或作为else if分支。
switch:开关语句。
case:开关语句分支。
case后面的值只能是整型或字符型的常量或者常量表达式。当有较多的case选项时,应该尽量把出现概率更大的case选项放在前面,以提升程序的执行效率。
default:开关语句中的其他分支。
for:循环语句。
do:循环语句中的循环体。
while:循环语句中的条件。
break:跳出循环。
continue:跳出本次循环,进入下一次循环。
goto:无条件跳转。
return:返回语句,可带参数。
return用来终止一个函数,并将return后面的值返回给函数的返回值。在函数内部,当执行到return语句的时候就会终止这个函数,并返回值,return语句后面的程序将不会再被执行。
return返回的值不能是存储在栈上的值(局部变量),因为局部变量在这个函数结束的时候被自动销毁,它的生命周期仅限于这个函数内部,所以不能作为return语句的返回值。
3. 存储类关键字(5个)
auto:声明自动变量,缺省时编译器默认为auto。
默认情况下,缺省时所有变量都是auto的。
extern:声明外部变量。
extern表示外部的,通过extern声明的变量或函数表示该变量或者函数是在外部文件定义的,告诉编译器在本文件中遇到该变量或者函数时,去其他文件中寻找变量或函数的定义。
register:声明寄存器变量。
定义寄存器变量,提高效率。register是建议型的指令,而不是命令型的指令,如果CPU有空闲寄存器,那么register就生效,如果没有空闲寄存器,那么register无效。该关键字请求编译器尽量的将变量存放在CPU内部寄存器中,这样在访问变量时不需要再通过内存寻址的方式访问,而是直接在寄存器中访问,大大提升了访问速度。但是CPU内部寄存器是有限的,所以register关键字只能是尽可能的请求编译器把变量存放在寄存器,而不是一定存放在寄存器。因为register关键字用于请求将数据存放在寄存器,所以使用register修饰符来修饰的变量必须是能被CPU寄存器所接受的类型,即register修饰的变量必须是长度小于或等于整形长度的值。同时,因为register修饰的变量可能会存放在寄存器中(也可能存放在内存中),所以不能对register修饰的变量进行取址操作,即不能通过取址操作符&来获取register修饰变量的地址。
static:声明静态变量。
-
修饰变量
static关键字可以修饰全局变量和局部变量,并且他们都会被存放在内存的静态区。
- 静态全局变量:限定变量的作用域为当前文件,即从变量定义之处开始一直到当前文件末尾,当前文件中该变量定义之前也无法使用(除非加extern声明),其他文件中即便是使用extern声明也无法使用。
- 静态局部变量:定义在函数体内部,并且作用域仅限于当前函数,当前文件该函数体外部无法使用。因为static修饰的静态变量存放在内存的静态区,所以函数运行结束这个静态变量也不会被销毁,函数下次被调用时这个变量的值依然存在,也就是我们说的静态局部变量只能被初始化一次,并且有记忆功能,下次调用函数时可以使用上次函数调用结束时静态局部变量的值。需要注意的是,普通的局部变量存放在栈区,函数调用结束变量就会被析构,也就是说普通局部变量的声明周期为定义该变量的函数体内。而静态局部变量存放在静态区,它的生命周期是整个程序执行期间,也就是说定义该静态局部变量的函数执行完毕,并不会析构静态局部变量,而是在当前程序执行完毕才会析构。
-
修饰函数
使用static关键字修饰函数可以将函数变为静态函数,也成为内部函数,静态函数的作用域为当前文件,在该文件之外无法访问。使用静态函数的好处是可以避免不同文件中函数同名引起的错误,但是会导致该文件之外无法调用的问题。
const:声明只读变量(C和C++区别)。
在C语言中,const定义的并不是真正的常量,而是具有只读属性的变量,其本质还是变量,只不过不可修改(实际上在C语言中是可以通过指针等其他方式间接修改的);而在C++中,const定义的是真正的常量,C++中是通过符号表一一对应的方式实现的。通过下面的例子也可以证明
const int NUM = 10;
char buf[NUM];
上面代码在C语言中编译不通过,但是在C++中编译通过。我们知道,定义数组时要指定数组大小,以便于编译器分配内存。在C语言中编译不通过也就证明了const定义的依然是变量,而不是常量。
编译器通常不会为const只读变量分配存储空间,而是将它们保存在符号表中,这使得它们成为一个编译期间的值,没有读写内存的操作,大大提高了效率。另外需要注意const与宏#define的区别
#define NUM 1 //宏定义一个常量
const int VAL = 2; //还没有将VAL放入内存中
int a = VAL; //此时为VAL分配内存,后面不再分配内存
int b = NUM; //预编译期间进行宏替换,分配内存
int c = VAL; //不会分配内存
int d = NUM; //宏替换,还会分配内存
从汇编的角度来看,const定义的只读变量只是给出了内存地址,而#define给出的是立即数。所以,在程序运行过程中,const定义的只读变量只有一份拷贝(全局只读变量存放在静态区,而不是堆栈),而#define定义的常量在内存中有多份拷贝。#define在预编译的时候进行宏替换,而const只读变量是在编译时确定它的值。另外,#define定义的常量没有类型,而const修饰的只读变量是有类型的。const 修饰的只读变量不能用来作为定义数组的维数,
也不能放在case 关键字后面。
最后,当const修饰指针时,放在不同位置所代表的含义也不同。
const int *p; //const修饰指针指向的内存,
//指针本身可变,指针指向的内存不可修改
int const *p; //const修饰指针指向的内存,
//指针本身可变,指针指向的内存不可修改
int * const p; //const修饰指针本身,
//指针指向不可修改,指针指向的内存可以修改
const int const *p; //指针本身和指针指向的内存都不可修改
4. 其他关键字(3个)
sizeof:计算一个对象所占的字节数。
sizeof在使用时虽然会加括号,但是他并不是函数,而是一个关键字。实际上,通过sizeof计算一个变量所占的内存大小时可以省略括号,sizeof(val)和sizeof val都可以,但是在计算数据类型的大小时必须加括号sizeof(int),否则的话会和类型扩展混淆,比如unsigned int就是扩展为无符号整型变量。因为sizeof不是函数,所以在使用时不需要包含任何头文件,但是sizeof是有返回值的,范围值类型为size_t,在32位操作系统下是unsigned int类型。
在计算一个字符串变量的大小时要区分sizeof与strlen的区别,strlen是一个函数,用于计算字符串的长度,所以不包含字符串最后的’\n’,而sizeof是计算变量所占内存大小,包括字符串结束符’\n’。
typedef:取别名。
typedef可以为一个数据类型定义一个新的名字,但是不能创建一个新的类型。与#define不同,typedef仅限于为数据类型取别名,而不能为表达式或具体的值取别名。#define发生在预处理阶段,typedef发生在编译阶段。
volatile:防止编译器优化,说明变量在程序执行中可被隐含地改变。
Volatile は揮発性を意味し、変更される変数は、変数の値がオペレーティング システム、ハードウェア周辺機器、または他のスレッドなどの特定の要因によって変更される可能性があることを示します。volatile キーワードによって変更された変数の場合、コンパイラは変数へのアクセスを最適化しません。
通常の変数の値を読み取る場合、アクセス速度を高速化するために、コンパイラは通常、レジスタに直接アクセスして値を取得するのではなく、キャッシュ内の変数の値を読み取ります。ただし、レジスタの値がプログラムによって変更されない場合もあります。たとえば、組み込み開発では開発ボードがよく使用されますが、多くの場合、レジスタの値はチップの周辺機器によって変更されます。このとき、プログラムではレジスタの値を変更していませんが、外部要因によりレジスタの値が変化しています。この種の変数にアクセスする場合、volatile キーワードが追加されていない場合、コンパイラはデフォルトでキャッシュ内の値を取得しますが、このときキャッシュ内の値は古い値であり、変数の実際の値はかわった。したがって、 volatile キーワードを追加することは、アクセスを最適化せず、変数の最新の値が毎回フェッチされるように、毎回変数のアドレスにある変数値にアクセスするようにコンパイラーに指示することになります。
例を挙げて説明しましょう
int val = 1;
int a = val;
int b = val;
上記のコードでは、変数 val が左辺値として使用されていない (つまり、変数 val の値がプログラム内で明示的に変更されていない) 場合、コンパイラーは変数 val の値が変更されていないとみなします。 val へのアクセスを最適化します。変数 a に値を代入する場合、コンパイラは val の値を取得して a に代入し、この値はキャッシュに配置されます。b に値を代入する場合、コンパイラーは val の値が変更されていないと判断するため、val 変数のアドレスで値をフェッチするのではなく、キャッシュ内の val の値を直接フェッチします。これにより、アクセス速度が大幅に向上します。 。これを行う前提は、val にアクセスする 2 つのステートメントの間に、左辺値として val を使用するステートメント (つまり、val の値を変更するステートメント) が存在しないことです。
val 変数が volatile 変数として変更される場合は異なります。
volatile int val = 1;
int a = val;
int b = val;
このとき、コンパイラは、プログラム中に val を左辺値として扱う文の有無に関わらず、val の値がいつでも変化する可能性があると考え、val 変数にアクセスするたびに、そのアドレスにアクセスすることになります。 val変数の。つまり、a に値を代入する場合、コンパイラは val のアドレスにある値をフェッチし、b に値を代入する場合も、コンパイラは val 変数のアドレスにある値をフェッチします。
一般に、レジスタ変数、ポート データ変数、およびマルチスレッド共有データ変数に揮発性変更を使用すると、変数の実際の値に安定してアクセスできます。