指针
# 07.指针
# 目录介绍
# 7.1 指针概念
# 7.1.1 通俗的案例
计算机中所有的数据都必须放在内存中,不同类型的数据占用的字节数不一样,例如 int 占用 4 个字节,char 占用 1 个字节。
为了正确地访问这些数据,必须为每个字节都编上号码,就像门牌号、身份证号一样,每个字节的编号是唯一的,根据编号可以准确地找到某个字节。
我们将内存中字节的编号称为地址(Address)或指针(Pointer)。地址从 0 开始依次增加,对于 32 位环境,程序能够使用的内存为 4GB,最小的地址为 0,最大的地址为 0XFFFFFFFF。
下面的代码演示了如何输出一个地址:
#include <stdio.h>
int main(){
int a = 100;
char str[20] = "yccoding.com";
printf("%#X, %#X\n", &a, str);
return 0;
}
2
3
4
5
6
7
8
运行结果: 0X28FF3C, 0X28FF10
a 是一个变量,用来存放整数,需要在前面加&来获得它的地址;str 本身就表示字符串的首地址,不需要加&。
# 7.1.2 一切都是地址
在计算机中,内存是由一系列连续的存储单元组成的,每个存储单元都有一个唯一的编号,这个编号就是地址。地址可以理解为内存中某个位置的标识符。
在 C 语言中,所有数据(变量、数组、指针、函数等)最终都存储在内存中,而内存中的每个存储单元都有地址。通过地址可以访问和操作这些数据。
# 7.1.3 指针基本概念
指针基本概念:指针的本质是一个变量,它存储的是另一个变量的内存地址。通过指针,我们可以间接地访问和操作内存中的数据。
- 指针变量:存储内存地址的变量。
- 地址运算符
&:获取变量的内存地址。 - 解引用运算符
*:访问指针指向的内存地址中的值。
# 7.1.4 综合案例与思考
综合案例:指针基础概念的直观理解
#include <stdio.h>
int main() {
// 案例1:理解地址与值的关系
int x = 42;
int *p = &x;
printf("=== 地址与值 ===\n");
printf("变量x的值: %d\n", x);
printf("变量x的地址: %p\n", (void *)&x);
printf("指针p的值: %p (存储的是x的地址)\n", (void *)p);
printf("指针p的地址: %p (p本身也占内存)\n", (void *)&p);
printf("*p的值: %d (通过p访问x的值)\n", *p);
// 案例2:不同类型变量的地址分布
printf("\n=== 不同类型的内存占用 ===\n");
char c = 'A';
short s = 100;
int i = 200;
double d = 3.14;
printf("char c: 地址=%p, 大小=%zu字节\n", (void *)&c, sizeof(c));
printf("short s: 地址=%p, 大小=%zu字节\n", (void *)&s, sizeof(s));
printf("int i: 地址=%p, 大小=%zu字节\n", (void *)&i, sizeof(i));
printf("double d: 地址=%p, 大小=%zu字节\n", (void *)&d, sizeof(d));
// 案例3:指针大小与平台关系
printf("\n=== 指针大小 ===\n");
printf("char* 大小: %zu字节\n", sizeof(char *));
printf("int* 大小: %zu字节\n", sizeof(int *));
printf("double* 大小: %zu字节\n", sizeof(double *));
printf("所有指针大小相同,取决于平台(32位=4字节,64位=8字节)\n");
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
原理说明:指针的本质是存储内存地址的变量。在32位系统上,地址空间为 4GB(2^32 字节),所以所有指针都是4字节。在64位系统上,理论地址空间为 2^64,但实际只使用48位,指针占8字节。无论指针指向什么类型的数据,指针本身的大小都是固定的,因为它只需要存储一个地址值。指针的类型信息告诉编译器:解引用时应该读取多少字节、指针算术运算时应该移动多少字节。
思考题:
int *p在32位和64位系统上,sizeof(p)和sizeof(*p)分别是多少?- 为什么需要指针类型?如果所有指针大小都一样,为什么不能用
int *代替double *? void *指针为什么不能直接解引用?它在C语言中扮演什么角色?
在 C 语言中,指针是一种特殊的变量,用于存储另一个变量的内存地址。
# 7.2.1 声明指针
指针,首先,它是一个值,这个值代表一个内存地址,因此指针相当于指向某个内存地址的路标。
字符表示指针,通常跟在类型关键字的后面,表示指针指向的是什么类型的值。比如,char表示一个指向字符的指针,float*表示一个指向float类型的值的指针。
int *p; // 声明一个指向 int 类型的指针
上面示例声明了一个变量p,它是一个指针,指向的内存地址存放的是一个整数。
指针声明注意事项:这种写法有一个地方需要注意,如果同一行声明两个指针变量,那么需要写成下面这样。
// 正确
int * foo, * bar;
// 错误
int* foo, bar;
2
3
4
5
# 7.2.2 初始化指针
声明指针变量之后,编译器会为指针变量本身分配一个内存空间,但是这个内存空间里面的值是随机的,也就是说,指针变量指向的值是随机的。
这时一定不能去读写指针变量指向的地址,因为那个地址是随机地址,很可能会导致严重后果。
int *p = 1; // 错误,因为p指向的那个地址是随机的,向这个随机地址里面写入1,会导致意想不到的结果。
int x = 10;
int *p = &x; // 将 p 初始化为 x 的地址
2
3
正确做法是指针变量声明后,必须先让它指向一个分配好的地址,然后再进行读写,这叫做指针变量的初始化。
为了防止读写未初始化的指针变量,可以养成习惯,将未初始化的指针变量设为NULL。
NULL在 C 语言中是一个常量,表示地址为0的内存空间,这个地址是无法使用的,读写该地址会报错。
int* p = NULL;
# 7.2.3 访问指针的值
访问指针的值是通过 解引用操作符* 来实现的。
#include <stdio.h>
int main() {
int x = 10; // 定义一个整型变量 x
int *p = &x; // 定义一个指针 p,存储变量 x 的地址
printf("Value of x: %d\n", x); // 输出 x 的值
printf("Address of x: %p\n", &x); // 输出 x 的地址
printf("Value of p: %p\n", p); // 输出指针 p 的值(即 x 的地址)
printf("Value pointed by p: %d\n", *p); // 通过指针访问 x 的值
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
输出结果如下:
Value of x: 10
Address of x: 0x7ffee3b5c8ac (示例地址,实际地址可能不同)
Value of p: 0x7ffee3b5c8ac
Value pointed by p: 10
2
3
4
# 7.3 指针使用
# 7.3.1 基本指针操作
- 是解引用运算符,用于访问指针指向的地址中的值。可以修改指针指向的变量的值。
#include <stdio.h>
int main() {
int x = 10;
int *p = &x;
printf("Address of x: %p\n", p);
printf("Value of x: %d\n", *p); //用于访问指针指向的地址中的值
*p = 20; // 通过指针修改 x 的值
printf("New value of x: %d\n", x);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
输出:
Address of x: 0x7ffee4b5c9ac
Value of x: 10
New value of x: 20
2
3
# 7.3.2 指针与数组
#include <stdio.h>
int main() {
int arr[] = {1, 2, 3, 4, 5};
int *p = arr; // 数组名是数组首元素的地址
for (int i = 0; i < 5; i++) {
printf("arr[%d] = %d\n", i, *(p + i));
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
输出:
arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[3] = 4
arr[4] = 5
2
3
4
5
# 7.3.3 指针与函数
(1) 指针作为函数参数
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
2
3
4
5
(2) 函数返回指针
int* createArray(int size) {
int *arr = (int *)malloc(size * sizeof(int));
return arr;
}
2
3
4
#include <stdio.h>
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 10, y = 20;
printf("Before swap: x = %d, y = %d\n", x, y);
swap(&x, &y);
printf("After swap: x = %d, y = %d\n", x, y);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
输出:
Before swap: x = 10, y = 20
After swap: x = 20, y = 10
2
# 7.3.4 *和&运算符
*和&运算符需要区别:
*这个符号除了表示指针以外,用来取出指针变量所指向的内存地址里面的值。*p就表示指针p所指向的那个值。&运算符用来取出一个变量所在的内存地址。
&运算符与*运算符互为逆运算,下面的表达式总是成立。
int i = 5;
if (i == *(&i)) // 正确
2
# 7.3.5 综合案例与思考
综合案例:指针的核心操作综合演练
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 通过指针交换两个变量
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
// 通过指针返回多个值
void min_max(int arr[], int size, int *min, int *max) {
*min = *max = arr[0];
for (int i = 1; i < size; i++) {
if (arr[i] < *min) *min = arr[i];
if (arr[i] > *max) *max = arr[i];
}
}
// 指针遍历数组
void print_array_ptr(int *begin, int *end) {
printf("[");
for (int *p = begin; p < end; p++) {
if (p != begin) printf(", ");
printf("%d", *p);
}
printf("]\n");
}
int main() {
// 案例1:交换操作
int a = 10, b = 20;
printf("交换前: a=%d, b=%d\n", a, b);
swap(&a, &b);
printf("交换后: a=%d, b=%d\n", a, b);
// 案例2:函数通过指针返回多个值
int arr[] = {34, 12, 67, 5, 89, 23, 45};
int n = sizeof(arr) / sizeof(arr[0]);
int min, max;
min_max(arr, n, &min, &max);
printf("\n数组: ");
print_array_ptr(arr, arr + n);
printf("最小值: %d, 最大值: %d\n", min, max);
// 案例3:指针与数组的等价关系
printf("\n数组下标 vs 指针算术:\n");
for (int i = 0; i < n; i++) {
printf("arr[%d]=%d, *(arr+%d)=%d, *(p+%d)=%d\n",
i, arr[i], i, *(arr + i), i, *(arr + i));
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
原理说明:指针是C语言最强大的特性之一。* 和 & 是一对互逆运算:& 取地址,* 解引用。arr[i] 在编译器内部会被转换为 *(arr + i),这就是为什么 i[arr] 也是合法的(等价于 *(i + arr))。通过指针参数可以让函数"返回"多个值——本质上是让函数直接写入调用者提供的内存地址。
思考题:
arr[i]和*(arr + i)完全等价,那么i[arr]合法吗?为什么?const int *p、int *const p、const int *const p三者有什么区别?- 返回局部变量的地址会怎样?为什么这是一个严重的错误?
# 7.4.1 常见指针类型
指针的类型决定了指针指向的数据类型以及指针的算术运算方式。
| 指针类型 | 说明 |
|---|---|
int * | 指向 int 类型的指针 |
float * | 指向 float 类型的指针 |
char * | 指向 char 类型的指针 |
void * | 通用指针,可以指向任何类型 |
# 7.4.2 指针类型转换
可以使用强制类型转换将一种指针类型转换为另一种指针类型。
int x = 10;
int *p = &x;
float *q = (float *)p; // 将 int * 转换为 float *
2
3
注意:指针类型转换可能导致未定义行为,需谨慎使用。
# 7.4.3 泛型指针
可以使用 void 指针(void pointer)来实现类似的泛型功能。void 指针是一种特殊类型的指针,可以指向任何类型的数据,因为它不指向特定的数据类型。
可以接受任意一种变量的地址。 但是,在使用时【必须】借助 “强制类型转换” 具体化数据类型。
#include <stdio.h>
void printValue(void *ptr, char type) {
switch(type) {
case 'i':
printf("Integer value: %d\n", *(int*)ptr);
break;
case 'f':
printf("Float value: %f\n", *(float*)ptr);
break;
case 'c':
printf("Character value: %c\n", *(char*)ptr);
break;
default:
printf("Unknown type\n");
}
}
int main() {
int intValue = 10;
float floatValue = 3.14;
char charValue = 'A';
printValue(&intValue, 'i');
printValue(&floatValue, 'f');
printValue(&charValue, 'c');
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 7.5 指针算术运算
指针本质上就是一个无符号整数,代表了内存地址。它可以进行运算,但是规则并不是整数运算的规则。
# 7.5.1 指针加减整数
int arr[] = {1, 2, 3, 4, 5};
int *p = arr;
p++; // p 指向 arr[1]
p += 2; // p 指向 arr[3]
2
3
4
5
指针与整数值的运算,表示指针的移动。
short* j;
j = (short*)0x1234;
j = j + 1; // 0x1236
2
3
上面示例中,j是一个指针,指向内存地址0x1234。由于0x1234本身是整数类型(int),跟j的类型(short*)并不兼容,所以强制使用类型投射,将0x1234转成short*。你可能以为j + 1等于0x1235,但正确答案是0x1236。原因是j + 1表示指针向内存地址的高位移动一个单位,而一个单位的short类型占据两个字节的宽度,所以相当于向高位移动两个字节。同样的,j - 1得到的结果是0x1232。
指针移动的单位,与指针指向的数据类型有关。数据类型占据多少个字节,每单位就移动多少个字节。
# 7.5.2 指针相减
int *p1 = &arr[1];
int *p2 = &arr[4];
int diff = p2 - p1; // diff = 3
2
3
相同类型的指针允许进行减法运算,返回它们之间的距离,即相隔多少个数据单位。
高位地址减去低位地址,返回的是正值;低位地址减去高位地址,返回的是负值。
这时,减法返回的值属于ptrdiff_t类型,这是一个带符号的整数类型别名,具体类型根据系统不同而不同。这个类型的原型定义在头文件stddef.h里面。
short* j1;
short* j2;
j1 = (short*)0x1234;
j2 = (short*)0x1236;
ptrdiff_t dist = j2 - j1;
printf("%td\n", dist); // 1
2
3
4
5
6
7
8
上面示例中,j1和j2是两个指向 short 类型的指针,变量dist是它们之间的距离,类型为ptrdiff_t,值为1,因为相差2个字节正好存放一个 short 类型的值。
# 7.5.3 指针比较运算
指针之间的比较运算通常用于比较指针所指向的内存地址的大小关系。指针比较运算符包括 ==、!=、<、<=、>、>=,可以用于比较指针的大小关系。
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40};
int *ptr1 = &arr[0];
int *ptr2 = &arr[2];
int *ptr3 = &arr[0];
if (ptr1 == ptr2) {
printf("ptr1 and ptr2 point to the same location\n");
} else {
printf("ptr1 and ptr2 point to different locations\n");
}
if (ptr1 < ptr2) {
printf("ptr1 points to a lower memory address than ptr2\n");
} else {
printf("ptr1 points to a higher memory address than ptr2\n");
}
if (ptr1 == ptr3) {
printf("ptr1 and ptr3 point to the same location\n");
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
我们使用指针比较运算符来比较这些指针的大小关系。
- ptr1 == ptr2:比较 ptr1 和 ptr2 是否指向相同的内存地址。
- ptr1 < ptr2:比较 ptr1 和 ptr2 所指向的内存地址的大小关系。
- ptr1 == ptr3:比较 ptr1 和 ptr3 是否指向相同的内存地址。
# 7.5.4 综合案例与思考
综合案例:指针算术运算的实际应用
#include <stdio.h>
#include <string.h>
int main() {
// 案例1:用指针算术遍历数组
int arr[] = {10, 20, 30, 40, 50};
int n = sizeof(arr) / sizeof(arr[0]);
printf("=== 指针算术遍历 ===\n");
int *p = arr;
while (p < arr + n) {
printf("地址: %p, 值: %d, 偏移: %td\n",
(void *)p, *p, p - arr);
p++;
}
// 案例2:指针相减计算距离
printf("\n=== 指针相减 ===\n");
int *first = &arr[0];
int *last = &arr[4];
printf("首尾指针差: %td 个元素\n", last - first);
printf("首尾字节差: %td 字节\n", (char *)last - (char *)first);
// 案例3:用指针实现字符串长度
char str[] = "Hello, C Pointer!";
char *s = str;
while (*s != '\0') s++;
printf("\n=== 指针实现strlen ===\n");
printf("字符串: \"%s\"\n", str);
printf("长度: %td (指针相减)\n", s - str);
printf("验证: %zu (strlen)\n", strlen(str));
// 案例4:不同类型指针的步长差异
printf("\n=== 指针步长 ===\n");
char *pc = (char *)0x1000;
short *ps = (short *)0x1000;
int *pi = (int *)0x1000;
double *pd = (double *)0x1000;
printf("char* + 1 = %p (步长%zu)\n", (void *)(pc+1), sizeof(char));
printf("short* + 1 = %p (步长%zu)\n", (void *)(ps+1), sizeof(short));
printf("int* + 1 = %p (步长%zu)\n", (void *)(pi+1), sizeof(int));
printf("double* + 1 = %p (步长%zu)\n", (void *)(pd+1), sizeof(double));
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
原理说明:指针算术运算的单位不是字节,而是所指向类型的大小。int *p; p+1 实际上是地址增加 sizeof(int) 个字节。这是C语言的设计精髓——让指针运算与数据类型自动匹配,使得遍历数组时只需简单的 p++。指针相减的结果类型是 ptrdiff_t(定义在 <stddef.h> 中),是一个有符号整数类型。只有指向同一数组(或数组末尾后一个位置)的指针之间的减法才是有定义的。
思考题:
int *p = arr; p + 3与(char *)p + 3的结果相同吗?各跳过了多少字节?- 两个指向不同数组的指针相减,结果有意义吗?C标准怎么说?
- 如何用指针算术实现一个简单的
memcpy函数?
字符串通常被表示为字符数组,而指针可以用来指向字符串的首地址。示例
char str[] = "Hello";
char *p = str;
char *ptr = "Hello";
printf("String: %s\n", p); //输出:String: Hello
printf("First character: %c\n", *p); //输出:First character: H
printf("%c\n", *ptr); // 输出 'H'
printf("%s\n", ptr); // 输出整个字符串 "Hello"
2
3
4
5
6
7
8
# 7.7 指针和数组
- 数组名是数组首元素的地址。
- 指针可以像数组一样使用下标访问元素。
示例
int arr[] = {1, 2, 3, 4, 5};
int *p = arr;
printf("arr[2] = %d\n", arr[2]);
printf("*(p + 2) = %d\n", *(p + 2));
2
3
4
5
输出:
arr[2] = 3
*(p + 2) = 3
2
# 7.7.1 指针数组
数组中的每个元素都是指针。
int *arr[3]; // 指针数组
int a = 1, b = 2, c = 3;
arr[0] = &a;
arr[1] = &b;
arr[2] = &c;
2
3
4
5
# 7.7.2 数组指针
指向数组的指针。
int arr[3] = {1, 2, 3};
int (*p)[3] = &arr; // 数组指针
2
# 7.8 多级指针
多级指针是指指针指向另一个指针的指针,也就是指针的指针。多级指针通常用于处理复杂的数据结构或动态内存分配等情况。
示例
int num = 10;
int *ptr1 = # // 一级指针,指向整型变量
int **ptr2 = &ptr1; // 二级指针,指向指针 ptr1
printf("Value of num: %d\n", num);
printf("Value of num using ptr1: %d\n", *ptr1);
printf("Value of num using ptr2: %d\n", **ptr2);
2
3
4
5
6
7
输出:
Value of num: 10
Value of num using ptr1: 10
Value of num using ptr2: 10
2
3
多级指针在某些情况下非常有用,特别是在处理动态内存分配或复杂数据结构时。
# 7.9 指针注意事项
# 7.9.1 空指针
指针未初始化时,应将其设置为 NULL。示例:
int *p = NULL;
# 7.9.2 野指针
野指针(dangling pointer)是指指向已释放的内存地址或未初始化的内存地址的指针。示例:
int *p = (int *)malloc(sizeof(int));
free(p);
// p 现在是野指针
2
3
野指针通常是由以下情况引起的:
- 释放内存后未将指针置为 NULL:当你释放了一个指针指向的内存后,如果没有将指针设置为 NULL,那么这个指针就成为了野指针。
- 指针超出作用域:如果指针指向的内存是在函数内部分配的,并且在函数返回后尝试访问该指针,那么这个指针也会成为野指针。
- 指针指向无效内存:如果指针指向的内存已经被释放或者指向未初始化的内存,那么这个指针也是野指针。
使用野指针可能导致程序崩溃、数据损坏或安全漏洞。为了避免野指针问题,最佳实践:
- 在释放内存后将指针置为 NULL。
- 避免在指针超出作用域后访问该指针。
- 确保指针指向的内存是有效的,避免访问已释放或未初始化的内存。
# 7.9.3 指针类型匹配
指针的类型必须与指向的数据类型匹配。示例:
int x = 10;
float *p = &x; // 错误:类型不匹配
2
# 7.9.4 指针算术运算
指针的算术运算基于指向的数据类型的大小。示例:
int arr[5];
int *p = arr;
p++; // p 指向 arr[1]
2
3
# 7.9.5 综合案例与思考
综合案例:指针安全使用的最佳实践
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 安全的内存分配封装
int *safe_malloc(size_t count) {
int *p = (int *)malloc(count * sizeof(int));
if (p == NULL) {
fprintf(stderr, "内存分配失败!\n");
return NULL;
}
memset(p, 0, count * sizeof(int)); // 初始化为0
return p;
}
// 安全释放宏(释放后置NULL)
#define SAFE_FREE(p) do { free(p); (p) = NULL; } while(0)
int main() {
// 案例1:避免野指针
printf("=== 避免野指针 ===\n");
int *p = safe_malloc(5);
if (p == NULL) return 1;
for (int i = 0; i < 5; i++) p[i] = (i + 1) * 10;
for (int i = 0; i < 5; i++) printf("%d ", p[i]);
printf("\n");
SAFE_FREE(p); // 释放并置NULL
printf("释放后 p = %p\n", (void *)p); // 输出 (nil)
// 案例2:检查空指针
printf("\n=== 空指针检查 ===\n");
if (p == NULL) {
printf("p已经是NULL,安全!\n");
}
// 案例3:避免返回局部变量的地址
printf("\n=== 正确返回动态内存 ===\n");
int *data = safe_malloc(3);
if (data) {
data[0] = 100; data[1] = 200; data[2] = 300;
printf("动态数组: %d, %d, %d\n", data[0], data[1], data[2]);
SAFE_FREE(data);
}
// 案例4:const指针防止误修改
printf("\n=== const保护 ===\n");
int value = 42;
const int *cp = &value; // 不能通过cp修改value
// *cp = 100; // 编译错误!
printf("const指针保护的值: %d\n", *cp);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
原理说明:指针相关的Bug是C语言程序中最常见且最难调试的问题。最佳实践包括:1)指针声明时初始化为 NULL;2)使用前检查是否为 NULL;3)free 后立即置 NULL(防止"悬空指针");4)用 const 限定不应被修改的指针参数;5)封装内存分配/释放函数,统一处理错误。工具如 Valgrind(检测内存泄漏)和 AddressSanitizer(检测越界访问)是开发中不可缺少的辅助。
思考题:
SAFE_FREE宏为什么用do { ... } while(0)包裹?直接写两条语句有什么问题?- 如何检测一个指针是否是野指针?为什么C语言无法做到100%检测?
restrict关键字(C99引入)对指针有什么约束?它如何帮助编译器优化?