我需要编写一个函数,它可以获取给定元素类型的任何二维数组。下面的C程序说明了我目前的方法。它定义明确吗?
#include <stdio.h>
#define LEN(arr) (sizeof (arr) / sizeof (arr)[0])
static void Print(const int *A, int m, int n)
{
int i, j;
for (i = 0; i < m; i++) {
for (j = 0; j < n; j++) {
printf("%d ", A[i * n + j]);
}
putchar('\n');
}
}
int main(void)
{
int A[2][3] = {{1, 2, 3}, {4, 5, 6}};
Print((int *) A, LEN(A), LEN(A[0]));
return 0;
}
答案 0 :(得分:2)
由于两个原因,您显示的代码的行为不是由C标准定义的。一,它违反了有关指针转换的规则。二,它违反了关于指针算法的规则。
首先,在(int *) A
的调用中考虑Print
。在此代码中,数组A
会自动转换为指向其第一个成员的指针,该成员是3 int
的数组。 C 2011 [N1570]第6.3.2.3节第7段允许这种转换,该条款说明“指向对象类型的指针可以转换为指向不同对象类型的指针。如果生成的指针未针对引用的类型正确对齐,则行为未定义。否则,当再次转换回来时,结果应该等于原始指针。“但是,这是关于转换指针的所有标准说明。当你在Print
中使用指针时,在表达式A[i * n + j]
中,没有定义行为,因为C标准中没有任何内容告诉我们这个转换指针是什么,除了它可以转换回原始类型。虽然指针已转换为int *
,但我们不知道转换的结果实际上是指向int
的{{1}}的可用指针。
其次,即使此指针是指向A[0][0]
的有效指针,标准也不会定义当您使用它在3 A[0][0]
数组之外进行索引时的行为。在表达式int
中,下标表达式被定义为等同于A[i * n + j]
6.5.2.1 2.当*((A) + (i * n + j)
被添加到i * n + j
时,语义由6.5控制.6 8,其中指示只要结果指向同一数组中的另一个元素或指向最后一个元素之外的一个元素,就可以使用指针算法来添加或减少指针。如果你有一个指向3 A
数组的第一个int
的指针,并且你添加了足够的指针以将指针移动到3 int
的下一个数组中,那么你已经超出了限制,并且行为未定义。 (由于将一个指向最后一个元素是合法的,您可以将3添加到指向int
的指针以获取指向A[0][0]
之外的指针,但不允许您应用A[0][2]
到这个指针,因为段落说“如果结果指向一个超过数组对象的最后一个元素,它不应该被用作被计算的一元*运算符的操作数。”)
也就是说,一些C实现将支持这些指针转换和这个算法。但代码不可移植;它并不严格符合C代码。
答案 1 :(得分:-2)
它定义明确,只需在调用中使用它就像这样:
Print((int *) A, 2, 3);
您只需要为方法提供行数和列数,而不是以字节为单位传递元素数量和每个元素的大小。
因为在编译时它知道你在谈论int
,所以当你在位置[i * n + j]
询问元素时,它已经知道你在int *
,然后添加数量int
的字节为你。
答案 2 :(得分:-3)
在C语言中,数组总是在内存中连续布局:
int A[2][3] = {{1, 2, 3}, {4, 5, 6}};
在内存中看起来像这样:
1 2 3 4 5 6
完全相同:
int A[6] = { 1, 2, 3, 4, 5, 6 };
您的方法是恰当的。
编辑:
有一些有趣的理论论据反对这种方法的有效性基于指针算术。然而,在我看来,构建一个编译器非常困难,该编译器一方面必须符合3.1.2.5并且无法解析相同类型/大小的对象的指针算术。 它可能意味着什么,但我提到编译并运行正确的代码:
GCC 4.6.3; GCC 5.3.0; GCC 5.4.0; GCC 6.3; C99 Strict GCC 6.3; Zapcc 5.0.0; GCC 7.1.1;
GCC 7.2.0; clang 3.8.0; gcc 5.0.4
Microsoft (R) C/C++ Optimizing Compiler Version 19.00.23506 for x64;
值得的是,我没有遇到过编译器会遇到August Karlstrom's
代码的问题。