Skip to content

Commit 2169618

Browse files
committed
vault backup: 2025-04-03 17:24:56
1 parent a11990f commit 2169618

File tree

4 files changed

+886
-0
lines changed

4 files changed

+886
-0
lines changed

src/develop/C/14_specifier.md

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
---
2+
category: C
3+
tags:
4+
- C
5+
---
6+
7+
# 变量说明符
8+
9+
C 语言允许声明变量的时候,加上一些特定的说明符(specifier),为编译器提供变量行为的额外信息。它的主要作用是帮助编译器优化代码,有时会对程序行为产生影响。
10+
11+
## const
12+
13+
`const`说明符表示变量是只读的,不得被修改。
14+
15+
```c
16+
const double PI = 3.14159;
17+
PI = 3; // 报错
18+
```
19+
20+
上面示例里面的`const`,表示变量`PI`的值不应改变。如果改变的话,编译器会报错。
21+
22+
对于数组,`const`表示数组成员不能修改。
23+
24+
```c
25+
const int arr[] = {1, 2, 3, 4};
26+
arr[0] = 5; // 报错
27+
```
28+
29+
上面示例中,`const`使得数组`arr`的成员无法修改。
30+
31+
对于指针变量,`const`有两种写法,含义是不一样的。如果`const`在`*`前面,表示指针指向的值不可修改。
32+
33+
```c
34+
// const 表示指向的值 *x 不能修改
35+
int const * x
36+
// 或者
37+
const int * x
38+
```
39+
40+
下面示例中,对`x`指向的值进行修改导致报错。
41+
42+
```c
43+
int p = 1
44+
const int* x = &p;
45+
46+
(*x)++; // 报错
47+
```
48+
49+
如果`const``*`后面,表示指针包含的地址不可修改。
50+
51+
```c
52+
// const 表示地址 x 不能修改
53+
int* const x
54+
```
55+
56+
下面示例中,对`x`进行修改导致报错。
57+
58+
```c
59+
int p = 1
60+
int* const x = &p;
61+
62+
x++; // 报错
63+
```
64+
65+
这两者可以结合起来。
66+
67+
```c
68+
const char* const x;
69+
```
70+
71+
上面示例中,指针变量`x`指向一个字符串。两个`const`意味着,`x`包含的内存地址以及`x`指向的字符串,都不能修改。
72+
73+
`const`的一个用途,就是防止函数体内修改函数参数。如果某个参数在函数体内不会被修改,可以在函数声明时,对该参数添加`const`说明符。这样的话,使用这个函数的人看到原型里面的`const`,就知道调用函数前后,参数数组保持不变。
74+
75+
```c
76+
void find(const int* arr, int n);
77+
```
78+
79+
上面示例中,函数`find`的参数数组`arr`有`const`说明符,就说明该数组在函数内部将保持不变。
80+
81+
有一种情况需要注意,如果一个指针变量指向`const`变量,那么该指针变量也不应该被修改。
82+
83+
```c
84+
const int i = 1;
85+
int* j = &i;
86+
*j = 2; // 报错
87+
```
88+
89+
上面示例中,`j`是一个指针变量,指向变量`i`,即`j``i`指向同一个地址。`j`本身没有`const`说明符,但是`i`有。这种情况下,`j`指向的值也不能被修改。
90+
91+
## static
92+
93+
`static`说明符对于全局变量和局部变量有不同的含义。
94+
95+
(1)用于局部变量(位于块作用域内部)。
96+
97+
`static`用于函数内部声明的局部变量时,表示该变量的值会在函数每次执行后得到保留,下次执行时不会进行初始化,就类似于一个只用于函数内部的全局变量。由于不必每次执行函数时,都对该变量进行初始化,这样可以提高函数的执行速度,详见《函数》一章。
98+
99+
(2)用于全局变量(位于块作用域外部)。
100+
101+
`static`用于函数外部声明的全局变量时,表示该变量只用于当前文件,其他源码文件不可以引用该变量,即该变量不会被链接(link)。
102+
103+
`static`修饰的变量,初始化时,值不能等于变量,必须是常量。
104+
105+
```c
106+
int n = 10;
107+
static m = n; // 报错
108+
```
109+
110+
上面示例中,变量`m``static`修饰,它的值如果等于变量`n`,就会报错,必须等于常量。
111+
112+
只在当前文件里面使用的函数,也可以声明为`static`,表明该函数只在当前文件使用,其他文件可以定义同名函数。
113+
114+
```c
115+
static int g(int i);
116+
```
117+
118+
## auto
119+
120+
`auto`说明符表示该变量的存储,由编译器自主分配内存空间,且只存在于定义时所在的作用域,退出作用域时会自动释放。
121+
122+
由于只要不是`extern`的变量(外部变量),都是由编译器自主分配内存空间的,这属于默认行为,所以该说明符没有实际作用,一般都省略不写。
123+
124+
```c
125+
auto int a;
126+
// 等同于
127+
int a;
128+
```
129+
130+
## extern
131+
132+
`extern`说明符表示,该变量在其他文件里面声明,没有必要在当前文件里面为它分配空间。通常用来表示,该变量是多个文件共享的。
133+
134+
```c
135+
extern int a;
136+
```
137+
138+
上面代码中,`a``extern`变量,表示该变量在其他文件里面定义和初始化,当前文件不必为它分配存储空间。
139+
140+
但是,变量声明时,同时进行初始化,`extern`就会无效。
141+
142+
```c
143+
// extern 无效
144+
extern int i = 0;
145+
146+
// 等同于
147+
int i = 0;
148+
```
149+
150+
上面代码中,`extern`对变量初始化的声明是无效的。这是为了防止多个`extern`对同一个变量进行多次初始化。
151+
152+
函数内部使用`extern`声明变量,就相当于该变量是静态存储,每次执行时都要从外部获取它的值。
153+
154+
函数本身默认是`extern`,即该函数可以被外部文件共享,通常省略`extern`不写。如果只希望函数在当前文件可用,那就需要在函数前面加上`static`
155+
156+
```c
157+
extern int f(int i);
158+
// 等同于
159+
int f(int i);
160+
```
161+
162+
## register
163+
164+
`register`说明符向编译器表示,该变量是经常使用的,应该提供最快的读取速度,所以应该放进寄存器。但是,编译器可以忽略这个说明符,不一定按照这个指示行事。
165+
166+
```c
167+
register int a;
168+
```
169+
170+
上面示例中,`register`提示编译器,变量`a`会经常用到,要为它提供最快的读取速度。
171+
172+
`register`只对声明在代码块内部的变量有效。
173+
174+
设为`register`的变量,不能获取它的地址。
175+
176+
```c
177+
register int a;
178+
int *p = &a; // 编译器报错
179+
```
180+
181+
上面示例中,`&a`会报错,因为变量`a`可能放在寄存器里面,无法获取内存地址。
182+
183+
如果数组设为`register`,也不能获取整个数组或任一个数组成员的地址。
184+
185+
```c
186+
register int a[] = {11, 22, 33, 44, 55};
187+
188+
int p = a; // 报错
189+
int a = *(a + 2); // 报错
190+
```
191+
192+
历史上,CPU 内部的缓存,称为寄存器(register)。与内存相比,寄存器的访问速度快得多,所以使用它们可以提高速度。但是它们不在内存之中,所以没有内存地址,这就是为什么不能获取指向它们的指针地址。现代编译器已经有巨大的进步,会尽可能优化代码,按照自己的规则决定怎么利用好寄存器,取得最佳的执行速度,所以可能会忽视代码里面的`register`说明符,不保证一定会把这些变量放到寄存器。
193+
194+
## volatile
195+
196+
`volatile`说明符表示所声明的变量,可能会预想不到地发生变化(即其他程序可能会更改它的值),不受当前程序控制,因此编译器不要对这类变量进行优化,每次使用时都应该查询一下它的值。硬件设备的编程中,这个说明符很常用。
197+
198+
```c
199+
volatile int foo;
200+
volatile int* bar;
201+
```
202+
203+
`volatile`的目的是阻止编译器对变量行为进行优化,请看下面的例子。
204+
205+
```c
206+
int foo = x;
207+
// 其他语句,假设没有改变 x 的值
208+
int bar = x;
209+
```
210+
211+
上面代码中,由于变量`foo``bar`都等于`x`,而且`x`的值也没有发生变化,所以编译器可能会把`x`放入缓存,直接从缓存读取值(而不是从 x 的原始内存位置读取),然后对`foo``bar`进行赋值。如果`x`被设定为`volatile`,编译器就不会把它放入缓存,每次都从原始位置去取`x`的值,因为在两次读取之间,其他程序可能会改变`x`
212+
213+
## restrict
214+
215+
`restrict`说明符允许编译器优化某些代码。它只能用于指针,表明该指针是访问数据的唯一方式。
216+
217+
```c
218+
int* restrict pt = (int*) malloc(10 * sizeof(int));
219+
```
220+
221+
上面示例中,`restrict`表示变量`pt`是访问 malloc 所分配内存的唯一方式。
222+
223+
下面例子的变量`foo`,就不能使用`restrict`修饰符。
224+
225+
```c
226+
int foo[10];
227+
int* bar = foo;
228+
```
229+
230+
上面示例中,变量`foo`指向的内存,可以用`foo`访问,也可以用`bar`访问,因此就不能将`foo`设为 restrict。
231+
232+
如果编译器知道某块内存只能用一个方式访问,可能可以更好地优化代码,因为不用担心其他地方会修改值。
233+
234+
`restrict`用于函数参数时,表示参数的内存地址之间没有重叠。
235+
236+
```c
237+
void swap(int* restrict a, int* restrict b) {
238+
int t;
239+
t = *a;
240+
*a = *b;
241+
*b = t;
242+
}
243+
```
244+
245+
上面示例中,函数参数声明里的 `restrict` 表示,参数 `a` 和参数 `b` 的内存地址没有重叠。
246+

0 commit comments

Comments
 (0)