第6章 数组 1. 数组的深入理解 1.1 数组的底层实现 数组是C语言中最基本的数据结构之一,其底层实现涉及内存布局、对齐要求和访问机制等核心概念。深入理解数组的底层实现对于编写高性能、安全的C代码至关重要。
1.1.1 数组的内存布局与对齐 数组在内存中以连续块形式存储,每个元素占用相同大小的内存空间,并遵循严格的对齐规则。
内存布局的底层细节 对于类型为 type 的数组 array_name[size],其内存布局具有以下特性:
连续存储 :元素在内存中连续排列,无间隙元素大小 :每个元素占用 sizeof(type) 字节地址计算 :array_name[i] 的地址为 base_address + i * sizeof(type)内存对齐 :数组起始地址必须满足 type 的对齐要求边界检查 :C语言不自动进行边界检查,需要程序员手动确保内存对齐的技术深度 内存对齐是数组性能优化的关键因素:
对齐要求的计算 :
基本类型的对齐要求通常等于其大小 结构体的对齐要求等于其成员中最大的对齐要求 可以使用 alignof 运算符获取类型的对齐要求 对齐对性能的影响 :
对齐的内存访问可以充分利用CPU的内存访问带宽 未对齐的访问可能导致CPU执行额外的内存读取操作 某些架构(如SPARC)甚至不支持未对齐的内存访问 编译器对齐控制 :
使用 __attribute__((aligned(n))) 强制指定对齐要求 使用 __attribute__((packed)) 取消对齐优化(仅在特殊情况下使用) 多级缓存与对齐 :
对齐的数组元素更容易落入同一个缓存行 跨缓存行的元素访问会导致额外的缓存未命中 合理的对齐可以提高缓存利用率 地址计算的底层优化 数组元素的地址计算是编译器优化的重要目标:
地址计算的汇编实现 :
对于 array[i],编译器会生成 base_address + i * element_size 的计算 现代编译器会使用移位操作优化乘法(如 i * 4 优化为 i << 2) 对于固定大小的数组,编译器会进行常量折叠优化 索引边界优化 :
对于循环中的数组访问,编译器会尝试优化边界检查 使用 size_t 类型作为索引可以避免符号扩展操作 循环不变量优化可以将地址计算的部分移到循环外部 SIMD指令与数组访问 :
对齐的数组可以更有效地使用SIMD指令 未对齐的数组可能需要额外的指令来处理边界情况 编译器会根据数组对齐情况选择最优的SIMD指令集 内存对齐的重要性 内存对齐对数组性能有显著影响:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <stdalign.h> printf ("int 对齐要求: %zu 字节\n" , alignof (int ));printf ("double 对齐要求: %zu 字节\n" , alignof (double ));printf ("struct { char c; int i; } 对齐要求: %zu 字节\n" , alignof (struct { char c; int i; })); int aligned_array[10 ] __attribute__((aligned(16 )));char buffer[sizeof (int ) * 10 + 1 ];int *unaligned_array = (int *)(buffer + 1 );
多级缓存与数组访问 现代处理器的多级缓存架构对数组访问性能有深刻影响:
缓存行 :通常为64字节,连续的数组元素可能落入同一缓存行预取机制 :处理器会自动预取连续的数组元素到缓存空间局部性 :数组的连续存储天然具有良好的空间局部性缓存命中率 :顺序访问数组时,缓存命中率接近100%1.1.2 数组的声明与初始化进阶 一维数组的高级初始化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int numbers[5 ] = {1 , 2 , 3 , 4 , 5 };int numbers[5 ] = {1 , 2 };int numbers[] = {1 , 2 , 3 , 4 , 5 };int numbers[5 ] = {[0 ] = 1 , [2 ] = 3 , [4 ] = 5 };#define INIT_ARRAY(type, arr, ...) \n\ type arr[] = {__VA_ARGS__} INIT_ARRAY(int , numbers, 1 , 2 , 3 , 4 , 5 );
多维数组的内存布局 多维数组在内存中以行优先(row-major)顺序线性存储:
1 2 3 4 5 6 // 二维数组 int matrix[3][3] 的内存布局 +-----+-----+-----+-----+-----+-----+-----+-----+-----+ | 0,0 | 0,1 | 0,2 | 1,0 | 1,1 | 1,2 | 2,0 | 2,1 | 2,2 | +-----+-----+-----+-----+-----+-----+-----+-----+-----+ | 基地址 | 基地址+4 | 基地址+8 | 基地址+12| ... | +-----+-----+-----+-----+-----+-----+-----+-----+-----+
复杂初始化示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int matrix[3 ][3 ] = { {1 , 2 , 3 }, {4 , 5 , 6 }, {7 , 8 , 9 } }; int matrix[3 ][3 ] = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 };int matrix[3 ][3 ] = { {1 , 2 }, {4 } }; int matrix[3 ][3 ] = { [0 ][0 ] = 1 , [1 ][1 ] = 5 , [2 ][2 ] = 9 };
1.1.3 数组的边界检查与安全性 C语言不自动进行数组边界检查,这是一把双刃剑:一方面提供了极高的性能,另一方面也带来了安全隐患。
边界检查的技术深度 边界检查的开销分析 :
每次数组访问都进行边界检查会增加约10-20%的执行时间 循环中的边界检查可以被编译器优化为单次检查 可以使用条件编译在调试版本中启用边界检查,在发布版本中禁用 静态边界检查 :
编译器可以在编译时检测到的边界检查错误 静态分析工具(如Clang Static Analyzer、Coverity)可以检测潜在的边界问题 类型安全的数组封装可以在编译时提供边界检查 动态边界检查 :
运行时边界检查可以捕获所有越界访问 内存安全工具(如AddressSanitizer、Valgrind)可以检测越界访问 自定义的安全数组结构可以提供运行时边界检查 高级边界检查实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #define STATIC_ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) #define ASSERT_ARRAY_BOUNDS(arr, idx) \ _Static_assert((void)sizeof(char[1 - 2*!!((idx) < 0 || (idx) > = STATIC_ARRAY_SIZE(arr))]), "数组索引越界" ) #define SAFE_ARRAY_ACCESS(arr, idx, default_val) \ (__builtin_expect(((idx) >= 0 && (idx) < STATIC_ARRAY_SIZE(arr)), 1) ? \ (arr)[idx] : (default_val)) #define DYNAMIC_ARRAY_ACCESS(arr, size, idx, default_val) \ (__builtin_expect(((idx) >= 0 && (idx) < (size)), 1) ? \ (arr)[idx] : (default_val))
安全数组的高级封装 1 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 typedef struct { void *data; size_t element_size; size_t size; size_t capacity; void (*destroy_element)(void *); } GenericSafeArray; GenericSafeArray* gsa_create (size_t element_size, size_t initial_capacity, void (*destroy_element)(void *)) { GenericSafeArray* arr = malloc (sizeof (GenericSafeArray)); if (!arr) return NULL ; arr->data = malloc (initial_capacity * element_size); if (!arr->data) { free (arr); return NULL ; } arr->element_size = element_size; arr->size = 0 ; arr->capacity = initial_capacity; arr->destroy_element = destroy_element; return arr; } void * gsa_get (const GenericSafeArray* arr, size_t index) { if (!arr || index >= arr->size) { return NULL ; } return (char *)arr->data + index * arr->element_size; } bool gsa_set (GenericSafeArray* arr, size_t index, const void * value) { if (!arr || !value || index >= arr->size) { return false ; } memcpy ((char *)arr->data + index * arr->element_size, value, arr->element_size); return true ; } bool gsa_push (GenericSafeArray* arr, const void * value) { if (!arr || !value) { return false ; } if (arr->size >= arr->capacity) { size_t new_capacity = arr->capacity * 2 ; if (new_capacity < 4 ) new_capacity = 4 ; void * new_data = realloc (arr->data, new_capacity * arr->element_size); if (!new_data) { return false ; } arr->data = new_data; arr->capacity = new_capacity; } memcpy ((char *)arr->data + arr->size * arr->element_size, value, arr->element_size); arr->size++; return true ; } bool gsa_remove (GenericSafeArray* arr, size_t index) { if (!arr || index >= arr->size) { return false ; } if (arr->destroy_element) { void * element = (char *)arr->data + index * arr->element_size; arr->destroy_element(element); } size_t elements_to_move = arr->size - index - 1 ; if (elements_to_move > 0 ) { memmove((char *)arr->data + index * arr->element_size, (char *)arr->data + (index + 1 ) * arr->element_size, elements_to_move * arr->element_size); } arr->size--; return true ; } void gsa_destroy (GenericSafeArray* arr) { if (!arr) return ; if (arr->destroy_element) { for (size_t i = 0 ; i < arr->size; i++) { void * element = (char *)arr->data + i * arr->element_size; arr->destroy_element(element); } } free (arr->data); free (arr); } #define DEFINE_SAFE_ARRAY_TYPE(type, prefix) \ typedef struct { GenericSafeArray base; } prefix##Array; \ static inline prefix##Array* prefix##_array_create(size_t initial_capacity) { return (prefix##Array*)gsa_create(sizeof (type), initial_capacity, NULL ); }\ \ static inline bool prefix##_array_push(prefix##Array* arr, type value) { return gsa_push((GenericSafeArray*)arr, &value); }\ \ static inline type prefix##_array_get(const prefix##Array* arr, size_t index) { type* ptr = (type*)gsa_get((const GenericSafeArray*)arr, index); return ptr ? *ptr : (type)0 ; }\ \ static inline void prefix##_array_destroy(prefix##Array* arr) { gsa_destroy((GenericSafeArray*)arr); } DEFINE_SAFE_ARRAY_TYPE(int , int ); void safe_array_example () { intArray* arr = int_array_create(4 ); if (arr) { int_array_push(arr, 10 ); int_array_push(arr, 20 ); int_array_push(arr, 30 ); for (size_t i = 0 ; i < ((GenericSafeArray*)arr)->size; i++) { printf ("Element %zu: %d\n" , i, int_array_get(arr, i)); } int_array_destroy(arr); } }
边界检查的性能优化 分支预测优化 :
使用 __builtin_expect 提示编译器预测分支方向 将边界检查放在循环外部,只检查一次 使用无分支的条件移动指令代替显式分支 内存布局优化 :
将数组大小和容量信息放在数组结构的开头 使用对齐的内存布局提高访问速度 对于固定大小的数组,使用编译时常量 编译器优化提示 :
使用 restrict 关键字告诉编译器指针不重叠 使用 inline 关键字内联小型访问函数 使用 register 关键字提示编译器将频繁使用的变量放入寄存器 1.1.4 数组的声明和初始化 一维数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 type array_name[size]; int numbers[5 ]; int numbers[5 ] = {1 , 2 , 3 , 4 , 5 }; int numbers[] = {1 , 2 , 3 , 4 , 5 }; int numbers[5 ] = {1 , 2 }; int numbers[5 ] = {0 }; int numbers[5 ] = {[0 ] = 1 , [2 ] = 3 , [4 ] = 5 };
二维数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 type array_name[rows][columns]; int matrix[3 ][3 ]; int matrix[3 ][3 ] = { {1 , 2 , 3 }, {4 , 5 , 6 }, {7 , 8 , 9 } }; int matrix[3 ][3 ] = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 }; int matrix[][3 ] = {{1 , 2 , 3 }, {4 , 5 , 6 }}; int matrix[3 ][3 ] = { [0 ][0 ] = 1 , [1 ][1 ] = 5 , [2 ][2 ] = 9 };
多维数组 多维数组的声明和初始化方式类似于二维数组,只是维度更多。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int cube[2 ][3 ][4 ]; int cube[2 ][3 ][4 ] = { { {1 , 2 , 3 , 4 }, {5 , 6 , 7 , 8 }, {9 , 10 , 11 , 12 } }, { {13 , 14 , 15 , 16 }, {17 , 18 , 19 , 20 }, {21 , 22 , 23 , 24 } } };
1.2 数组的访问 数组元素通过索引访问,索引从 0 开始。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int numbers[5 ] = {1 , 2 , 3 , 4 , 5 };printf ("第一个元素:%d\n" , numbers[0 ]); printf ("最后一个元素:%d\n" , numbers[4 ]); numbers[2 ] = 10 ; for (int i = 0 ; i < 5 ; i++){ printf ("numbers[%d] = %d\n" , i, numbers[i]); } int matrix[3 ][3 ] = {{1 , 2 , 3 }, {4 , 5 , 6 }, {7 , 8 , 9 }};for (int i = 0 ; i < 3 ; i++){ for (int j = 0 ; j < 3 ; j++) { printf ("matrix[%d][%d] = %d\n" , i, j, matrix[i][j]); } }
1.2.1 数组的边界检查 C 语言不进行数组边界检查,访问超出数组范围的元素会导致未定义行为。
1 2 int numbers[5 ] = {1 , 2 , 3 , 4 , 5 };numbers[10 ] = 100 ;
为了避免数组越界,可以采取以下措施:
使用常量定义数组大小 在循环中使用正确的边界条件 使用静态分析工具检测潜在的越界访问 1.3 数组的特性 数组大小固定 - 数组一旦声明,大小不能改变连续存储 - 数组元素在内存中连续存储零基索引 - 数组索引从 0 开始数组名是常量指针 - 数组名指向数组的第一个元素,是一个常量指针数组长度计算 - 使用 sizeof 运算符可以计算数组的总大小和单个元素的大小1 2 3 4 int numbers[5 ];printf ("数组总大小:%zu 字节\n" , sizeof (numbers)); printf ("单个元素大小:%zu 字节\n" , sizeof (numbers[0 ])); printf ("数组长度:%zu\n" , sizeof (numbers) / sizeof (numbers[0 ]));
1.4 变长数组(VLA) C99 引入了变长数组(Variable Length Array),允许使用变量作为数组大小。
1 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 #include <stdio.h> void print_array (int n, int arr[n]) { for (int i = 0 ; i < n; i++) { printf ("%d " , arr[i]); } printf ("\n" ); } int main (void ) { int size; printf ("请输入数组大小:" ); scanf ("%d" , &size); int numbers[size]; for (int i = 0 ; i < size; i++) { numbers[i] = i + 1 ; } print_array(size, numbers); return 0 ; }
注意 :
变长数组不能初始化 变长数组的大小在运行时确定 变长数组只在块作用域内有效 不是所有编译器都支持变长数组(如 MSVC) 1.5 数组作为函数参数 当数组作为函数参数传递时,实际上传递的是数组第一个元素的地址。这一机制对函数设计和性能优化有着重要影响。
1.5.1 数组作为函数参数的底层机制 数组退化 :
数组作为函数参数时会退化为指向其第一个元素的指针 编译器会将 void func(int arr[]) 转换为 void func(int *arr) 因此,函数无法通过 sizeof(arr) 获取数组的实际大小 参数传递的汇编实现 :
数组参数在传递时,只需要将数组的基地址压栈 相比传递整个数组,这大大减少了参数传递的开销 对于大型数组,这种优化尤为重要 类型信息的保留 :
虽然数组会退化为指针,但指针的类型信息会被保留 这使得编译器能够正确处理指针算术和类型转换 例如,int * 和 float * 的指针算术步长不同 1.5.2 多维数组作为函数参数的高级处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 void process_matrix (int matrix[][3 ], int rows) { } void process_matrix (int (*matrix)[3 ], int rows) { } void process_3d_array (int arr[][2 ][3 ], int depth) { } void process_3d_array (int (*arr)[2 ][3 ], int depth) { } void process_variable_matrix (int rows, int cols, int matrix[rows][cols]) { }
1.5.3 数组参数的性能优化 传递数组长度 :
始终将数组长度作为单独的参数传递 使用 size_t 类型作为长度参数,提高可移植性 避免使用全局变量传递数组长度 使用 restrict 关键字 :
restrict 关键字告诉编译器指针不与其他指针重叠这使得编译器能够进行更积极的优化 例如:void process_array(int *restrict arr, size_t size) 内联函数优化 :
对于小型数组处理函数,使用 inline 关键字 内联可以消除函数调用开销,提高性能 但要注意内联可能导致代码膨胀 编译时边界检查 :
使用静态断言在编译时检查数组边界 例如:_Static_assert(sizeof(arr) == expected_size, "数组大小不正确") 1.5.4 数组参数的安全性 边界检查 :
在函数内部始终检查数组索引的边界 使用 assert 或条件检查确保索引有效 对于外部输入,进行严格的边界验证 指针有效性 :
检查传入的数组指针是否为 NULL 对于空指针,采取适当的错误处理策略 例如:if (!arr) return error_code; 类型安全 :
避免将不同类型的数组传递给同一个函数 使用 void* 时,确保正确的类型转换 考虑使用泛型编程技术提高类型安全性 1.5.5 高级数组参数传递技术 1 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 typedef struct { int *data; size_t size; } ArrayWithSize; void process_array_safe (ArrayWithSize arr) { for (size_t i = 0 ; i < arr.size; i++) { } } void generic_array_process (void *data, size_t element_size, size_t count, void (*process)(void *)) { char *ptr = (char *)data; for (size_t i = 0 ; i < count; i++) { process(ptr + i * element_size); } } void process_int (void *data) { int *value = (int *)data; *value = *value * 2 ; } void example () { int arr[] = {1 , 2 , 3 , 4 , 5 }; generic_array_process(arr, sizeof (int ), 5 , process_int); }
1.5.6 数组参数的现代C特性 可变长度数组(VLA) :
C99引入的可变长度数组可以作为函数参数 例如:void process_vla(int n, int arr[n]) 注意:并非所有编译器都支持VLA(如MSVC) 复合字面量 :
复合字面量可以直接作为数组参数传递 例如:process_array((int[]){1, 2, 3, 4, 5}, 5) 这提供了一种方便的方式传递临时数组 数组指针的类型别名 :
使用 typedef 创建数组指针的类型别名 例如:typedef int (*IntArrayPtr)[10]; 这提高了代码的可读性和可维护性 2. 指针的深入理解 2.1 指针的基本概念 指针是一种存储内存地址的变量。指针变量本身也占用内存空间,其大小取决于系统架构(32位系统为4字节,64位系统为8字节)。深入理解指针的底层实现和内存模型对于编写高性能、安全的C代码至关重要。
2.1.1 指针的底层实现与内存模型 指针的存储 :
指针在内存中存储的是另一个变量的内存地址 指针的大小取决于系统架构:32位系统中为4字节,64位系统中为8字节 指针的大小与所指向的数据类型无关 指针的类型 :
指针的类型决定了指针算术的步长和解引用的行为 例如,int * 类型的指针每次递增会移动4字节(在32位系统中) void * 类型的指针不包含类型信息,需要显式转换后才能解引用指针的表示 :
指针值通常以十六进制形式表示内存地址 空指针(NULL)在内存中表示为全0 野指针是指向无效内存区域的指针,使用野指针会导致未定义行为 2.1.2 指针的内存布局 对于一个类型为 type * 的指针变量 p,其内存布局如下:
1 2 3 4 5 6 7 +---------------------+ | p | +---------------------+ | 内存地址: &p | +---------------------+ | 值: 指向的内存地址 | +---------------------+
其中,&p 是指针变量本身的内存地址,而指针变量的值是它指向的内存地址。
2.1.3 指针的高级操作 多级指针 :
指向指针的指针,用于处理二维数组、动态分配的多维数据等 例如:int **pp 表示指向int指针的指针 多级指针的使用需要谨慎,避免过度复杂 函数指针 :
指向函数的指针,用于实现回调、策略模式等 例如:void (*callback)(int) 表示指向接受int参数、返回void的函数的指针 函数指针是C语言实现多态的重要手段 指针与数组的关系 :
数组名可以视为指向数组第一个元素的常量指针 指针可以像数组一样使用下标访问 例如:p[0] 等价于 *p,p[1] 等价于 *(p+1) 指针与结构体 :
指针可以指向结构体,用于动态分配和操作结构体 使用 -> 运算符访问结构体指针的成员 例如:struct Person *p; p->name 等价于 (*p).name 2.1.4 指针的安全使用 空指针检查 :
在解引用指针前始终检查是否为NULL 例如:if (p != NULL) { *p = value; } 可以使用 assert 宏在调试版本中进行检查 野指针的避免 :
始终将指针初始化为NULL或有效的内存地址 在释放指针后将其设置为NULL 避免使用已经释放的指针 指针的边界检查 :
在使用指针进行算术运算时,确保不会越界 对于动态分配的内存,跟踪其大小并进行边界检查 指针的类型安全 :
避免随意的指针类型转换 使用 void * 作为通用指针类型,但在使用前进行正确的类型转换 考虑使用 restrict 关键字提高指针的类型安全性和优化潜力 2.1.5 指针的性能优化 指针算术优化 :
编译器会优化指针算术,例如将 p + 1 转换为适当的地址计算 对于固定步长的指针算术,编译器会生成高效的汇编代码 指针别名优化 :
使用 restrict 关键字告诉编译器指针不与其他指针重叠 这使得编译器能够进行更积极的优化,如加载/存储优化 指针与缓存 :
局部变量的指针通常指向栈内存,具有较高的缓存命中率 全局变量的指针指向数据段,缓存命中率可能较低 合理的指针使用可以提高缓存利用率 指针与分支预测 :
指针的条件判断会影响分支预测 避免在热点路径中使用难以预测的指针条件 2.1.6 指针的底层实现示例 1 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 printf ("Size of int *: %zu bytes\n" , sizeof (int *));printf ("Size of char *: %zu bytes\n" , sizeof (char *));printf ("Size of double *: %zu bytes\n" , sizeof (double *));int arr[] = {1 , 2 , 3 , 4 , 5 };int *p = arr;printf ("Address of arr[0]: %p\n" , (void *)p);printf ("Address of arr[1]: %p\n" , (void *)(p + 1 ));printf ("Difference: %td bytes\n" , (char *)(p + 1 ) - (char *)p);int x = 10 ;int *px = &x;int **ppx = &px;printf ("Value of x: %d\n" , **ppx);int add (int a, int b) { return a + b; } int subtract (int a, int b) { return a - b; } int (*operation)(int , int );operation = add; printf ("5 + 3 = %d\n" , operation(5 , 3 ));operation = subtract; printf ("5 - 3 = %d\n" , operation(5 , 3 ));struct Point { int x; int y; }; struct Point p1 = {10 , 20 };struct Point *pp1 = &p1;printf ("Point: (%d, %d)\n" , pp1->x, pp1->y);int arr2[] = {1 , 2 , 3 , 4 , 5 };int *p2 = arr2;printf ("arr2[0] = %d\n" , *p2);printf ("arr2[1] = %d\n" , *(p2 + 1 ));printf ("arr2[2] = %d\n" , p2[2 ]);char str[] = "Hello" ;char *p3 = str;while (*p3 != '\0' ) { printf ("%c" , *p3); p3++; } printf ("\n" );int *dynamic_arr = malloc (5 * sizeof (int ));if (dynamic_arr != NULL ) { for (int i = 0 ; i < 5 ; i++) { dynamic_arr[i] = i * 10 ; } for (int i = 0 ; i < 5 ; i++) { printf ("dynamic_arr[%d] = %d\n" , i, dynamic_arr[i]); } free (dynamic_arr); dynamic_arr = NULL ; }
2.2 指针的声明和初始化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 type *pointer_name; int *p; float *fp; char *cp; int x = 10 ;int *p = &x; int *p = NULL ; int *p = 0 ; int *p = nullptr;
2.3 指针的解引用 使用 * 运算符可以访问指针指向的变量的值。
1 2 3 4 5 6 7 8 9 10 int x = 10 ;int *p = &x;printf ("x 的值:%d\n" , x); printf ("p 的值:%p\n" , p); printf ("*p 的值:%d\n" , *p); *p = 20 ; printf ("x 的新值:%d\n" , x);
2.4 指针的算术运算 指针支持以下算术运算:
加法 - 指针 + 整数减法 - 指针 - 整数指针相减 - 两个指针相减比较 - 比较两个指针的大小2.4.1 指针算术的本质 指针算术的结果取决于指针指向的类型。对于一个类型为 type * 的指针 p,p + n 表示指向 p 指向的位置之后的第 n 个 type 类型的元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int numbers[] = {1 , 2 , 3 , 4 , 5 };int *p = numbers; printf ("*p = %d\n" , *p); printf ("p 的地址:%p\n" , (void *)p);p++; printf ("*p = %d\n" , *p); printf ("p 的地址:%p\n" , (void *)p);p += 2 ; printf ("*p = %d\n" , *p); printf ("p 的地址:%p\n" , (void *)p);int *q = &numbers[4 ]; printf ("q - p = %ld\n" , q - p); if (p < q){ printf ("p 指向的元素在 q 指向的元素之前\n" ); }
2.5 指针的类型转换 指针的类型转换需要谨慎使用,不正确的类型转换可能导致未定义行为。
1 2 3 4 5 6 7 8 9 10 11 12 int x = 10 ;int *p = &x;void *vp = p; int *p2 = (int *)vp; char *cp = (char *)&x; printf ("*cp = %d\n" , *cp);
2.6 空指针和野指针 2.6.1 空指针 空指针是指向空地址的指针,通常使用 NULL 或 0 表示。
1 2 3 4 5 6 7 int *p = NULL ;if (p != NULL ){ }
2.6.2 野指针 野指针是指向无效内存地址的指针,通常由以下原因产生:
1 2 3 4 5 6 7 8 9 10 11 int *p; int *p = (int *)malloc (sizeof (int ));free (p);p = NULL ; int numbers[5 ];int *p = &numbers[5 ];
2.7 指针数组 指针数组是一个存储指针的数组。
1 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 type *array_name[size]; char *names[] = { "Alice" , "Bob" , "Charlie" , "David" }; for (int i = 0 ; i < 4 ; i++){ printf ("%s\n" , names[i]); } int a = 10 , b = 20 , c = 30 ;int *ptr_array[] = {&a, &b, &c};for (int i = 0 ; i < 3 ; i++){ printf ("%d\n" , *ptr_array[i]); }
2.8 指向指针的指针 指向指针的指针是一种存储指针地址的变量。
1 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 type **pointer_name; int x = 10 ;int *p = &x;int **pp = &p;printf ("x = %d\n" , x); printf ("*p = %d\n" , *p); printf ("**pp = %d\n" , **pp); **pp = 20 ; printf ("x = %d\n" , x); int **matrix;int rows = 3 , cols = 3 ;matrix = (int **)malloc (rows * sizeof (int *)); for (int i = 0 ; i < rows; i++){ matrix[i] = (int *)malloc (cols * sizeof (int )); } for (int i = 0 ; i < rows; i++){ for (int j = 0 ; j < cols; j++) { matrix[i][j] = i * cols + j + 1 ; } } for (int i = 0 ; i < rows; i++){ free (matrix[i]); } free (matrix);
2.9 函数指针 函数指针是指向函数的指针,可以用于回调函数、函数表等场景。
1 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 type (*pointer_name)(parameters); int add (int a, int b) { return a + b; } int subtract (int a, int b) { return a - b; } int (*operation)(int , int );operation = add; printf ("3 + 4 = %d\n" , operation(3 , 4 )); operation = subtract; printf ("3 - 4 = %d\n" , operation(3 , 4 )); int (*operations[])(int , int ) = {add, subtract};printf ("3 + 4 = %d\n" , operations[0 ](3 , 4 )); printf ("3 - 4 = %d\n" , operations[1 ](3 , 4 ));
3. 数组和指针的关系 3.1 数组名作为指针 数组名本质上是一个指向数组第一个元素的常量指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 int numbers[] = {1 , 2 , 3 , 4 , 5 };int *p = numbers; for (int i = 0 ; i < 5 ; i++){ printf ("%d " , *(p + i)); } printf ("\n" );printf ("numbers[2] = %d\n" , numbers[2 ]); printf ("*(numbers + 2) = %d\n" , *(numbers + 2 ));
3.2 数组下标和指针算术的等价性 对于数组 arr 和整数 i,arr[i] 等价于 *(arr + i)。
1 2 3 4 5 6 7 8 9 10 11 int numbers[] = {1 , 2 , 3 , 4 , 5 };int *p = numbers;numbers[2 ] = 10 ; *(numbers + 2 ) = 10 ; p[2 ] = 10 ; *(p + 2 ) = 10 ; 2 [numbers] = 10 ;
3.3 多维数组与指针 多维数组在内存中是线性存储的,其元素按行优先顺序排列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int matrix[3 ][3 ] = { {1 , 2 , 3 }, {4 , 5 , 6 }, {7 , 8 , 9 } }; int (*p)[3 ] = matrix;printf ("matrix[1][2] = %d\n" , matrix[1 ][2 ]); printf ("*(*(p + 1) + 2) = %d\n" , *(*(p + 1 ) + 2 )); printf ("p[1][2] = %d\n" , p[1 ][2 ]);
4. 动态内存管理 4.1 内存分配函数 C 语言提供了以下动态内存分配函数:
malloc - 分配指定大小的内存calloc - 分配指定数量和大小的内存,并初始化为 0realloc - 调整已分配内存的大小free - 释放已分配的内存这些函数都在 stdlib.h 头文件中声明。
4.2 内存分配示例 1 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #include <stdio.h> #include <stdlib.h> int main (void ) { int *arr = (int *)malloc (5 * sizeof (int )); if (arr == NULL ) { printf ("内存分配失败\n" ); return 1 ; } for (int i = 0 ; i < 5 ; i++) { arr[i] = i + 1 ; } for (int i = 0 ; i < 5 ; i++) { printf ("%d " , arr[i]); } printf ("\n" ); int *new_arr = (int *)realloc (arr, 10 * sizeof (int )); if (new_arr == NULL ) { printf ("内存分配失败\n" ); free (arr); return 1 ; } arr = new_arr; for (int i = 5 ; i < 10 ; i++) { arr[i] = i + 1 ; } for (int i = 0 ; i < 10 ; i++) { printf ("%d " , arr[i]); } printf ("\n" ); free (arr); int *arr2 = (int *)calloc (5 , sizeof (int )); if (arr2 == NULL ) { printf ("内存分配失败\n" ); return 1 ; } for (int i = 0 ; i < 5 ; i++) { printf ("%d " , arr2[i]); } printf ("\n" ); free (arr2); return 0 ; }
4.3 内存管理的常见问题 4.3.1 内存泄漏 内存泄漏是指程序分配了内存但没有释放,导致内存使用量不断增加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void leak_memory (void ) { int *arr = (int *)malloc (100 * sizeof (int )); } int main (void ) { for (int i = 0 ; i < 1000 ; i++) { leak_memory(); } return 0 ; }
4.3.2 双重释放 双重释放是指对同一块内存多次调用 free,导致未定义行为。
1 2 3 int *p = (int *)malloc (sizeof (int ));free (p);free (p);
4.3.3 悬垂指针 悬垂指针是指指向已释放内存的指针。
1 2 3 int *p = (int *)malloc (sizeof (int ));free (p);*p = 10 ;
4.4 内存管理的最佳实践 始终检查内存分配是否成功 使用完内存后及时释放 释放内存后将指针置为空 避免双重释放 使用适当的内存分配函数 考虑使用内存池管理频繁分配的小内存 5. 高级应用 5.1 动态数组 动态数组是使用动态内存分配实现的可变大小的数组。
1 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 #include <stdio.h> #include <stdlib.h> typedef struct { int *data; int size; int capacity; } DynamicArray; DynamicArray *create_dynamic_array (int initial_capacity) { DynamicArray *array = (DynamicArray *)malloc (sizeof (DynamicArray)); if (array == NULL ) { return NULL ; } array ->data = (int *)malloc (initial_capacity * sizeof (int )); if (array ->data == NULL ) { free (array ); return NULL ; } array ->size = 0 ; array ->capacity = initial_capacity; return array ; } void add_element (DynamicArray *array , int element) { if (array ->size >= array ->capacity) { int new_capacity = array ->capacity * 2 ; int *new_data = (int *)realloc (array ->data, new_capacity * sizeof (int )); if (new_data == NULL ) { return ; } array ->data = new_data; array ->capacity = new_capacity; } array ->data[array ->size++] = element; } void free_dynamic_array (DynamicArray *array ) { if (array != NULL ) { free (array ->data); free (array ); } } int main (void ) { DynamicArray *array = create_dynamic_array(5 ); if (array == NULL ) { printf ("内存分配失败\n" ); return 1 ; } for (int i = 0 ; i < 10 ; i++) { add_element(array , i + 1 ); } for (int i = 0 ; i < array ->size; i++) { printf ("%d " , array ->data[i]); } printf ("\n" ); free_dynamic_array(array ); return 0 ; }
5.2 指针数组的高级应用 指针数组可以用于实现字符串表、命令表等。
1 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 #include <stdio.h> void help_command (void ) { printf ("帮助命令\n" ); } void version_command (void ) { printf ("版本命令\n" ); } void exit_command (void ) { printf ("退出命令\n" ); } typedef struct { char *name; void (*func)(void ); } Command; Command commands[] = { {"help" , help_command}, {"version" , version_command}, {"exit" , exit_command}, {NULL , NULL } }; void execute_command (char *name) { for (int i = 0 ; commands[i].name != NULL ; i++) { if (strcmp (name, commands[i].name) == 0 ) { commands[i].func(); return ; } } printf ("未知命令\n" ); } int main (void ) { execute_command("help" ); execute_command("version" ); execute_command("exit" ); execute_command("unknown" ); return 0 ; }
5.3 函数指针的高级应用 函数指针可以用于实现回调函数、策略模式等。
1 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 56 57 58 59 60 61 62 63 64 #include <stdio.h> typedef int (*CompareFunction) (int , int ) ;int ascending (int a, int b) { return a - b; } int descending (int a, int b) { return b - a; } void sort (int arr[], int size, CompareFunction compare) { for (int i = 0 ; i < size - 1 ; i++) { for (int j = 0 ; j < size - i - 1 ; j++) { if (compare(arr[j], arr[j + 1 ]) > 0 ) { int temp = arr[j]; arr[j] = arr[j + 1 ]; arr[j + 1 ] = temp; } } } } void print_array (int arr[], int size) { for (int i = 0 ; i < size; i++) { printf ("%d " , arr[i]); } printf ("\n" ); } int main (void ) { int numbers[] = {5 , 2 , 8 , 1 , 9 }; int size = sizeof (numbers) / sizeof (numbers[0 ]); printf ("原始数组:" ); print_array(numbers, size); sort(numbers, size, ascending); printf ("升序排序:" ); print_array(numbers, size); sort(numbers, size, descending); printf ("降序排序:" ); print_array(numbers, size); return 0 ; }
6. 性能优化 6.1 数组和指针操作的性能比较 在大多数情况下,数组下标访问和指针算术的性能差异很小,因为编译器会进行优化。然而,在某些特定场景下,指针算术可以提供更细粒度的控制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 for (int i = 0 ; i < size; i++){ sum += arr[i]; } for (int *p = arr, *end = arr + size; p < end; p++){ sum += *p; } register int *p asm ("r12" ) = arr;register int *end asm ("r13" ) = arr + size;register int s asm ("r14" ) = 0 ;for (; p < end; p++) { s += *p; } sum = s;
6.2 缓存友好的数据结构和算法 缓存友好的代码可以显著提高程序性能,特别是在处理大型数组时。
6.2.1 空间局部性优化 空间局部性是指程序倾向于访问最近访问过的内存位置附近的内存。
1 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 for (int i = 0 ; i < rows; i++){ for (int j = 0 ; j < cols; j++) { sum += matrix[i][j]; } } for (int j = 0 ; j < cols; j++){ for (int i = 0 ; i < rows; i++) { sum += matrix[i][j]; } } #define BLOCK_SIZE 32 for (int i = 0 ; i < rows; i += BLOCK_SIZE) { for (int j = 0 ; j < cols; j += BLOCK_SIZE) { for (int ii = i; ii < i + BLOCK_SIZE && ii < rows; ii++) { for (int jj = j; jj < j + BLOCK_SIZE && jj < cols; jj++) { sum += matrix[ii][jj]; } } } }
6.2.2 时间局部性优化 时间局部性是指程序倾向于多次访问同一内存位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int value = arr[i];for (int j = 0 ; j < 10 ; j++){ sum += value * j; } for (int j = 0 ; j < 10 ; j++){ sum += arr[i] * j; } for (int i = 0 ; i < size - 3 ; i += 4 ) { sum += arr[i] + arr[i+1 ] + arr[i+2 ] + arr[i+3 ]; } for (; i < size; i++) { sum += arr[i]; }
6.3 SIMD指令优化 SIMD(Single Instruction Multiple Data)指令可以同时处理多个数据元素,显著提高数组操作性能。
1 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 #include <immintrin.h> float sum_array_avx2 (const float *arr, size_t size) { __m256 sum_vec = _mm256_setzero_ps(); size_t i; for (i = 0 ; i + 7 < size; i += 8 ) { __m256 vec = _mm256_loadu_ps(arr + i); sum_vec = _mm256_add_ps(sum_vec, vec); } float sum = 0.0f ; float temp[8 ]; _mm256_storeu_ps(temp, sum_vec); for (int j = 0 ; j < 8 ; j++) { sum += temp[j]; } for (; i < size; i++) { sum += arr[i]; } return sum; } int sum_array_sse (const int *arr, size_t size) { __m128i sum_vec = _mm_setzero_si128(); size_t i; for (i = 0 ; i + 3 < size; i += 4 ) { __m128i vec = _mm_loadu_si128((__m128i*)(arr + i)); sum_vec = _mm_add_epi32(sum_vec, vec); } int sum = 0 ; int temp[4 ]; _mm_storeu_si128((__m128i*)temp, sum_vec); for (int j = 0 ; j < 4 ; j++) { sum += temp[j]; } for (; i < size; i++) { sum += arr[i]; } return sum; }
6.4 内存池设计与数组管理 内存池可以显著提高频繁分配和释放小型数组的性能。
1 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 typedef struct { void *pool; void *free_list; size_t block_size; size_t pool_size; size_t block_count; } MemoryPool; bool pool_init (MemoryPool *pool, size_t block_size, size_t block_count) { if (block_size < sizeof (void *)) { block_size = sizeof (void *); } pool->pool = malloc (block_size * block_count); if (!pool->pool) { return false ; } pool->block_size = block_size; pool->pool_size = block_size * block_count; pool->block_count = block_count; pool->free_list = NULL ; char *current = (char *)pool->pool; for (size_t i = 0 ; i < block_count; i++) { void *block = current; current += block_size; *(void **)block = pool->free_list; pool->free_list = block; } return true ; } void * pool_alloc (MemoryPool *pool) { if (!pool->free_list) { return NULL ; } void *block = pool->free_list; pool->free_list = *(void **)block; return block; } void pool_free (MemoryPool *pool, void *ptr) { if (!ptr) { return ; } if (ptr < pool->pool || ptr >= (char *)pool->pool + pool->pool_size) { return ; } *(void **)ptr = pool->free_list; pool->free_list = ptr; } void pool_destroy (MemoryPool *pool) { free (pool->pool); pool->pool = NULL ; pool->free_list = NULL ; } typedef struct { int *data; size_t size; } SmallArray; SmallArray* alloc_small_array (MemoryPool *pool, size_t size) { SmallArray *array = (SmallArray*)pool_alloc(pool); if (!array ) { return NULL ; } array ->size = size; array ->data = (int *)pool_alloc(pool); if (!array ->data) { pool_free(pool, array ); return NULL ; } return array ; } void free_small_array (MemoryPool *pool, SmallArray *array ) { if (!array ) { return ; } if (array ->data) { pool_free(pool, array ->data); } pool_free(pool, array ); }
6.5 内存访问模式的高级优化 减少内存访问次数 :将频繁访问的数据存储在寄存器或局部变量中使用连续内存 :使用数组而非链表,提高空间局部性避免内存碎片 :合理规划内存分配,避免频繁分配和释放小内存内存对齐 :合理安排数据结构,提高内存访问效率预取技术 :使用软件预取指令提前加载数据到缓存分支预测优化 :减少数组操作中的分支预测失败1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void process_large_array (const int *arr, size_t size, int *result) { for (size_t i = 0 ; i < size; i++) { if (i + 64 < size) { __builtin_prefetch(&arr[i + 64 ], 0 , 0 ); } result[i] = arr[i] * 2 + 1 ; } } void process_array_branchless (int *arr, size_t size) { for (size_t i = 0 ; i < size; i++) { int value = arr[i]; int is_negative = value < 0 ; arr[i] = (value ^ -is_negative) + is_negative; } }
7. 常见错误和调试 7.1 数组越界 数组越界是最常见的错误之一,可能导致程序崩溃或安全漏洞。
1 2 3 4 5 6 int numbers[5 ];for (int i = 0 ; i <= 5 ; i++) { numbers[i] = i; }
7.2 野指针和悬垂指针 野指针和悬垂指针可能导致程序崩溃或未定义行为。
1 2 3 4 5 6 7 8 int *p;*p = 10 ; int *p = (int *)malloc (sizeof (int ));free (p);*p = 10 ;
7.3 内存泄漏 内存泄漏会导致程序内存使用量不断增加,最终可能导致程序崩溃。
1 2 3 4 5 6 void function (void ) { int *p = (int *)malloc (100 * sizeof (int )); }
7.4 调试技巧 使用断言 :在关键位置添加断言,检查指针是否为空、数组下标是否越界等使用内存调试工具 :如 Valgrind、AddressSanitizer 等工具检测内存错误打印调试信息 :在关键位置打印指针值、数组下标等信息代码审查 :仔细检查内存分配和释放的配对情况8. 数组的高级应用 8.1 高性能数组算法 8.1.1 二分查找的高级实现 1 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 56 57 58 59 60 61 62 63 64 65 #include <stdio.h> #include <stddef.h> #include <string.h> void *binary_search_generic (const void *key, const void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)) { const char *ptr = (const char *)base; size_t left = 0 ; size_t right = nmemb - 1 ; while (left <= right) { size_t mid = left + (right - left) / 2 ; const void *mid_ptr = ptr + mid * size; int cmp = compar(key, mid_ptr); if (cmp == 0 ) { return (void *)mid_ptr; } else if (cmp < 0 ) { right = mid - 1 ; } else { left = mid + 1 ; } } return NULL ; } int compare_int (const void *a, const void *b) { return *(const int *)a - *(const int *)b; } int compare_str (const void *a, const void *b) { return strcmp (*(const char **)a, *(const char **)b); } int binary_search_optimized (const int *arr, size_t size, int target) { int left = 0 ; int right = size - 1 ; if (size == 0 ) return -1 ; if (target < arr[0 ]) return -1 ; if (target > arr[size-1 ]) return -1 ; while (left <= right) { int mid = left + (right - left) / 2 ; int mid_val = arr[mid]; if (mid_val == target) { return mid; } int left_cond = (target > mid_val); left = left_cond ? (mid + 1 ) : left; right = left_cond ? right : (mid - 1 ); } return -1 ; }
8.1.2 数组排序的高级实现 1 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 void swap (int *a, int *b) { int temp = *a; *a = *b; *b = temp; } void insertion_sort (int *arr, size_t left, size_t right) { for (size_t i = left + 1 ; i <= right; i++) { int key = arr[i]; size_t j = i - 1 ; while (j >= left && arr[j] > key) { arr[j + 1 ] = arr[j]; j--; } arr[j + 1 ] = key; } } void heapify (int *arr, size_t n, size_t i) { size_t largest = i; size_t left = 2 * i + 1 ; size_t right = 2 * i + 2 ; if (left < n && arr[left] > arr[largest]) { largest = left; } if (right < n && arr[right] > arr[largest]) { largest = right; } if (largest != i) { swap(&arr[i], &arr[largest]); heapify(arr, n, largest); } } void heap_sort (int *arr, size_t left, size_t right) { size_t n = right - left + 1 ; for (size_t i = n / 2 - 1 ; i >= 0 ; i--) { heapify(arr + left, n, i); } for (size_t i = n - 1 ; i > 0 ; i--) { swap(&arr[left], &arr[left + i]); heapify(arr + left, i, 0 ); } } size_t partition (int *arr, size_t left, size_t right) { int pivot = arr[right]; size_t i = left - 1 ; for (size_t j = left; j < right; j++) { if (arr[j] <= pivot) { i++; swap(&arr[i], &arr[j]); } } swap(&arr[i + 1 ], &arr[right]); return i + 1 ; } void introsort_helper (int *arr, size_t left, size_t right, size_t max_depth) ;void introsort (int *arr, size_t size) { size_t max_depth = 2 * (size_t )__builtin_log2(size); introsort_helper(arr, 0 , size - 1 , max_depth); } void introsort_helper (int *arr, size_t left, size_t right, size_t max_depth) { if (right - left <= 16 ) { insertion_sort(arr, left, right); } else if (max_depth == 0 ) { heap_sort(arr, left, right); } else { size_t pivot = partition(arr, left, right); if (pivot > left) { introsort_helper(arr, left, pivot - 1 , max_depth - 1 ); } introsort_helper(arr, pivot + 1 , right, max_depth - 1 ); } }
8.2 并行数组处理 8.2.1 OpenMP 并行数组操作 1 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 #include <omp.h> long long parallel_sum (const int *arr, size_t size) { long long sum = 0 ; #pragma omp parallel { long long local_sum = 0 ; #pragma omp for for (size_t i = 0 ; i < size; i++) { local_sum += arr[i]; } #pragma omp atomic sum += local_sum; } return sum; } void parallel_matrix_multiply (const double *A, const double *B, double *C, size_t n, size_t m, size_t p) { #pragma omp parallel for collapse(2) for (size_t i = 0 ; i < n; i++) { for (size_t j = 0 ; j < p; j++) { double sum = 0.0 ; for (size_t k = 0 ; k < m; k++) { sum += A[i * m + k] * B[k * p + j]; } C[i * p + j] = sum; } } }
8.3 数组的内存优化技术 8.3.1 数组压缩技术 1 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 56 57 58 typedef struct { uint64_t *bits; size_t size; size_t capacity; } BitArray; BitArray *bit_array_create (size_t capacity) { BitArray *ba = malloc (sizeof (BitArray)); if (!ba) return NULL ; ba->size = 0 ; ba->capacity = capacity; ba->bits = calloc ((capacity + 63 ) / 64 , sizeof (uint64_t )); if (!ba->bits) { free (ba); return NULL ; } return ba; } void bit_array_set (BitArray *ba, size_t index, bool value) { if (index >= ba->capacity) return ; size_t word = index / 64 ; size_t bit = index % 64 ; if (value) { ba->bits[word] |= (1ULL << bit); } else { ba->bits[word] &= ~(1ULL << bit); } if (index >= ba->size) { ba->size = index + 1 ; } } bool bit_array_get (const BitArray *ba, size_t index) { if (index >= ba->capacity) return false ; size_t word = index / 64 ; size_t bit = index % 64 ; return (ba->bits[word] & (1ULL << bit)) != 0 ; } void bit_array_destroy (BitArray *ba) { if (ba) { free (ba->bits); free (ba); } }
8.4 示例:高性能矩阵运算库 1 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 typedef struct { size_t rows; size_t cols; double *data; bool owner; } Matrix; Matrix matrix_create (size_t rows, size_t cols) { Matrix mat; mat.rows = rows; mat.cols = cols; mat.data = malloc (rows * cols * sizeof (double )); mat.owner = true ; return mat; } void matrix_destroy (Matrix *mat) { if (mat && mat->owner && mat->data) { free (mat->data); mat->data = NULL ; } } Matrix matrix_multiply (const Matrix *a, const Matrix *b) { assert(a->cols == b->rows); Matrix result = matrix_create(a->rows, b->cols); if (!result.data) { return result; } for (size_t i = 0 ; i < result.rows * result.cols; i++) { result.data[i] = 0.0 ; } const size_t BLOCK_SIZE = 32 ; for (size_t i = 0 ; i < a->rows; i += BLOCK_SIZE) { for (size_t j = 0 ; j < b->cols; j += BLOCK_SIZE) { for (size_t k = 0 ; k < a->cols; k += BLOCK_SIZE) { size_t i_end = i + BLOCK_SIZE < a->rows ? i + BLOCK_SIZE : a->rows; size_t j_end = j + BLOCK_SIZE < b->cols ? j + BLOCK_SIZE : b->cols; size_t k_end = k + BLOCK_SIZE < a->cols ? k + BLOCK_SIZE : a->cols; for (size_t ii = i; ii < i_end; ii++) { for (size_t kk = k; kk < k_end; kk++) { double ak = a->data[ii * a->cols + kk]; for (size_t jj = j; jj < j_end; jj++) { result.data[ii * result.cols + jj] += ak * b->data[kk * b->cols + jj]; } } } } } } return result; } int lu_decomposition (double *A, size_t n, int *pivot) { for (size_t i = 0 ; i < n; i++) { pivot[i] = i; } for (size_t i = 0 ; i < n; i++) { double max_val = 0.0 ; size_t max_idx = i; for (size_t j = i; j < n; j++) { double val = fabs (A[j * n + i]); if (val > max_val) { max_val = val; max_idx = j; } } if (max_val == 0.0 ) { return -1 ; } if (max_idx != i) { for (size_t j = 0 ; j < n; j++) { double temp = A[i * n + j]; A[i * n + j] = A[max_idx * n + j]; A[max_idx * n + j] = temp; } int temp = pivot[i]; pivot[i] = pivot[max_idx]; pivot[max_idx] = temp; } for (size_t j = i + 1 ; j < n; j++) { A[j * n + i] /= A[i * n + i]; for (size_t k = i + 1 ; k < n; k++) { A[j * n + k] -= A[j * n + i] * A[i * n + k]; } } } return 0 ; } void lu_solve (const double *A, const int *pivot, double *b, size_t n) { for (size_t i = 0 ; i < n; i++) { double temp = b[pivot[i]]; b[pivot[i]] = b[i]; b[i] = temp; for (size_t j = 0 ; j < i; j++) { b[i] -= A[i * n + j] * b[j]; } } for (size_t i = n - 1 ; i < n; i--) { for (size_t j = i + 1 ; j < n; j++) { b[i] -= A[i * n + j] * b[j]; } b[i] /= A[i * n + i]; } }
9. 小结 本章深入介绍了 C 语言中数组和指针的概念、使用方法和高级应用。数组和指针是 C 语言的核心概念,掌握它们对于理解 C 语言的工作原理和编写高效的 C 程序至关重要。
9.1 关键知识点 数组 :连续存储的相同类型元素的集合,大小固定,零基索引指针 :存储内存地址的变量,支持算术运算和解引用数组和指针的关系 :数组名是指向数组第一个元素的常量指针,数组下标访问等同于指针算术动态内存管理 :使用 malloc、calloc、realloc 和 free 进行内存的分配和释放高级应用 :动态数组、指针数组、函数指针、回调函数等性能优化 :缓存友好的数据结构和算法,内存访问模式的优化常见错误 :数组越界、野指针、悬垂指针、内存泄漏等9.2 学习建议 多写代码 :通过实际编程练习掌握数组和指针的使用深入理解 :理解数组和指针的内存布局和工作原理注意安全 :始终检查指针是否为空,避免数组越界,正确管理内存学习调试 :掌握使用调试工具检测和修复内存错误的技巧阅读优秀代码 :学习开源项目中数组和指针的使用技巧数组和指针是 C 语言的强大特性,也是 C 语言的难点所在。通过本章的学习,希望读者能够深入理解数组和指针的概念,掌握它们的使用方法,编写更加高效、安全的 C 程序。
在后续章节中,我们将学习字符串处理、结构体和共用体等复合数据类型,这些内容都与数组和指针密切相关。