C语言中如何将二维数组传入期望一维数组的函数?
问题背景
这是K.N.King所著《C语言程序设计:现代方法》中的一道习题:
假设有如下存储一周每小时温度读数的数组,每行代表一天的读数:
int temperatures[7][24];
编写一条语句,调用search函数在整个temperatures数组中查找值32。
我编写的代码包含之前习题实现的search函数,以及调用该函数的main()函数。search函数用于遍历数组,检查是否存在与传入参数“key”相等的元素。该代码编译执行后能输出预期结果:当保留temperatures[5][22] = 32;时会打印“32 was found”,注释掉该赋值语句则无输出。
注意,search函数接收一维数组,但习题要求处理二维数组。我最初调用函数时未显式添加(int*)类型转换,编译时出现如下提示:
- 编译器警告:
passing argument 1 of search from incompatible pointer type - 附加提示:
expected 'const int *' but argument is of type 'int * [24]'
我的代码
#include <stdbool.h> #include <stdio.h> bool search(const int a[], int n, int key); int main(void){ int temperatures[7][24] = {0}; temperatures[5][22] = 32; bool has32 = search((int*)temperatures, 7 * 24, 32); if(has32) printf("32 was found"); return 0; } bool search(const int a[], int n, int key){ const int *p; for(p = a; p < a + n; p++) if(*p == key) return true; return false; }
问题解答
1. 为何会出现这些警告/提示?
C语言中数组名在多数场景下会退化为指向数组首元素的指针。对于二维数组temperatures[7][24],它的首元素是一个包含24个int的一维数组,因此退化为的指针类型是int (*)[24](指向长度为24的int数组的指针),而非int*。
而search函数的第一个参数const int a[],会被编译器解析为const int*(函数参数中的数组声明等价于指针)。直接传递temperatures时,是把int (*)[24]类型的指针传给了期望const int*的参数,两者类型不兼容,因此编译器抛出类型不匹配的警告。
2. 既然类型不兼容,为何不是错误?
C语言设计允许一定程度的隐式指针转换(只要指针的底层存储宽度一致,比如多数系统中所有指针类型大小相同)。编译器将这种情况标记为警告而非错误,是因为它判断代码大概率能运行(指针值都是数组首地址),但存在潜在风险,提醒开发者可能存在逻辑问题。不过这种宽容处理并非标准C强制要求,不同编译器表现可能不同。
3. 使用(int*)类型转换是否正确/安全?
在这个特定场景下,该转换是正确且安全的:
- 二维数组在内存中是连续存储的:
temperatures的7个一维数组在内存中紧密排列,整个数组可看作一个包含7*24=168个int的一维数组。 (int*)temperatures将指向二维数组首元素的指针强制转换为指向int的指针,指向数组第一个元素的地址,后续遍历168个int的操作正好覆盖整个二维数组,不会出现越界问题。
但这种安全性依赖于二维数组的连续存储特性,如果是动态分配的指针数组(比如int** temps),这种转换会直接出错,因为指针数组的元素是分散的指针,而非连续的int数据。
4. 保持search函数不变,如何从根源避免问题?
可以通过获取二维数组中第一个int元素的地址,直接得到int*类型的指针,无需强制转换,完全符合C语言类型规则:
- 方法一:利用一维数组名退化特性,取
temperatures[0](它会退化为int*)bool has32 = search(temperatures[0], 7 * 24, 32); - 方法二:直接取第一个int元素的地址
&temperatures[0][0],类型就是int*bool has32 = search(&temperatures[0][0], 7 * 24, 32);
这两种方式都能直接传递匹配类型的指针给search函数,不会产生任何编译警告,从根源解决了类型不兼容问题。
内容的提问来源于stack exchange,提问作者0jnats3

