在将多维数组传递给函数时,难以理解传递的元素

我在某处读到以下数组可以以这种方式传递给以下函数,如下所示,但是我不明白数组中的哪些元素正好传递给函数。
这些是数组

int array[NROWS][NCOLUMNS]; int **array1; int **array2; int *array3; int (*array4)[NCOLUMNS]; 

这些是function:

 f1(int a[][NCOLUMNS], int m, int n); f2(int *aryp, int nrows, int ncolumns); f3(int **pp, int m, int n); 

我读到的网站提到我们可以通过以下方式将以下数组传递给以下函数:

 f2(&array[0][0], NROWS, NCOLUMNS); f2(*array2, nrows, ncolumns); f2(array3, nrows, ncolumns); f2(*array4, nrows, NCOLUMNS); f3(array1, nrows, ncolumns); f3(array2, nrows, ncolumns); 

array1array2不是一个指针数组? 所以当你把它们传递给f3 ,所有的指针都会被传递掉? 关于array2传递给f2f2在forms参数中有一个普通指针,但是array2是一个指针数组,那么在将指针数组传递给f2时,如何访问各个行和列? 当传递array4时,你将如何访问单个行和列? array4是一系列1D数组的指针,用于函数f2

你可以在C中使用数组来表示在语言概念方面并不总是数组。

C是70年代的语言。 与更抽象的后代相比,它非常接近机器实现。 因此,如果您想了解除了容易混淆的类似语法元素之外的内容,您必须考虑实现。

指针和数组都可以通过(方括号)括号表示

括号表示法无疑是有用的,但就指针和数组之间的混淆而言,它是所有邪恶的根源。

f[i]也将“工作”指针和数组,虽然底层机制会有所不同,我们将会看到。

指针和数组之间的关系

让我们从变量声明开始。

指针

float * f只是告诉编译器符号f有一天会引用未知数量的浮点数。

f未初始化。 您可以自行决定实际数据的位置,并将f设置为指向它们。

指针算术和括号表示法

请记住,在向指针添加/减去值时,单位是指向类型的大小。

 float * f; float * f3 = f+3; // internal value: f + 3 * sizeof (float) // these two statements are identical *f3 = 1; *(f+3) = 1; 

因为当你想从指针引用连续数据时写*(f+i)很笨,所以可以使用括号表示法

 f[3] = 1; // equivalent to *(f+3) = 1; 

无论使用何种符号,f [3]的地址都计算如下:

@f[ 3 ] = f + 3 * sizeof (float)

您可以将f function上视为(动态)数组,但是当C看到它时, 它仍然是一个指针 ,通过使其看起来像数组的语法引用。

数组

float f[10]仍然告诉编译器f将引用一些浮点数,但它也是

  • 在适当的位置分配所请求的浮动数
    • 如果f是自动局部变量,则在堆栈上
    • 如果f是全局变量或静态变量,则在静态数据(aka BSS)中
  • 将符号f视为指向第一个浮点值的常量指针

尽管数组创建语法可能令人困惑,但数组在编译时始终具有已知的固定大小。

例如, float f[] = {2,4,8}声明一个长度为3的数组,相当于float f[ 3 ] = {2,4,8} 。 为方便起见,可以省略维度:长度反映了初始化程序的数量,而不强制程序员明确地重复它。

不幸的是, []表示法也可以在某些其他情况下引用指针 (稍后将详细介绍)。

括号表示法和数组

括号表示法是访问数组内容的最自然方式。

引用数组时,编译器知道它是一个数组。 然后它可以根据数组的第一个元素访问数据,如下所示:

@f[ 3 ] = f + 3 * sizeof (float)

对于一维数组(但仅限于这种情况!),您可以看到地址计算与指针完全相同。

数组作为指针

由于一个数组也被认为是一个(常量)指针,你可以使用一个数组来初始化一个指针,认为反过来显然是假的(因为数组是一个常量指针,因此它的值不能改变)。

插图

 void test (void) { float* f1; float f2[10]; float f3[]; // <-- compiler error : dimension not known float f4[] = {5,7}; // creates float f4[2] with f4[0]=5 and f4[1]=7 f1[3] = 1234; // <--- write to a random memory location. You're in trouble f2[3] = 5678; // write into the space reserved by the compiler // obtain 10 floats from the heap and set f1 to point to them f1 = (float *) calloc (10, sizeof(float)); f1[3] = 1234; // write into the space reserved by you // make f1 an alias of f2 (f1 will point to the same data as f2) f1 = f2; // f2 is a constant pointer to the array data printf ("%g", f1[3]); // will print "5678", as set through f2 // f2 cannot be changed f2 = f1; // <-- compiler error : incompatible types 'float[10]' / 'float *' } 

走向多维

让我们将我们的例子扩展到二维案例:

 float f2[3][10]; // 2d array of floats float ** f1; // pointer to pointer to float f1 = f2; // <-- the compiler should not allow that, but it does! f2[2][5] = 1234; // set some array value printf ("%g\n", f2[2][5]); // no problem accessing it printf ("%g\n",f1[2][5]); // bang you're dead 

让我们看看这里发生了什么

当你声明float f2[3][10] ,编译器会将30个必需的浮点数分配为一个连续的块。 前10个浮点数代表f [0],下一个10个浮点数[1]等。

当你编写f2[ 2 ][ 5 ] ,编译器仍然知道f是一个数组 ,所以它可以像这样计算所需浮点数的有效地址:

@f2[ 2 ][ 5 ] = f + ( 2 * 10 + 5 ) * sizeof (float)

如果指针具有适当数量的引用级别,您还可以通过多个括号访问指针

在引用指针时,编译器只是连续应用指针算术:

 float h = f1[2][5]; 

相当于:

 float * g = f1[2]; // equivalent to g = *(f1+2) float h = g[5]; // equivalent to h = *(g +5) 

f1[ 2 ][ 5 ]由编译器处理为*(*(f1+ 2 )+ 5 ) 。 最终地址将按如下方式计算:

@f1[ 2 ][ 5 ] = *(f + 2 * sizeof (float *)) + 5 * sizeof (float)

你问过它,你知道了

除了相同的括号表示法之外,还有两种截然不同的实现。

显然,当试图通过f1访问f2数据时,结果将是灾难性的。

编译器将从f2[2]获得第3个浮点数,将其视为指针,将其加20并尝试引用结果地址。

如果你通过这种错误的初始化指针写一些值,如果你得到一个访问冲突而不是默默地破坏一些随机的四个字节的内存,那么就要认为自己很幸运。

不幸的是,即使基础数据结构无法正确访问,除非编译器知道f2是一个数组, f2 仍然被认为是一个常量float**指针。

在一个理想的世界里它不应该,但在C(唉!)中,它是。

这意味着您可以在没有编译器抱怨的情况下为数组指定指针 ,即使结果没有意义。

函数调用

数组和指针都可以作为参数传递给函数。

但是,为了避免像前面示例中那样的灾难性错误解释,您必须让编译器知道传递给函数的是一个数组还是一个指针。

同样,由于编译器将数组视为常量指针,您将被允许执行愚蠢的操作,例如声明数组并将其传递给类似指针的函数。

空方括号

更糟糕的是,函数参数声明的语法允许使用括号,使得数组和指针之间的混淆更加可能。

 void f (float f1[]); 

完全按照处理

 void f (float * f1); 

即使变量声明

 float f1[]; 

将产生错误而不是将其视为声明float * f的替代方法。

您可以说[]表示法允许指定指针 ,但仅限于函数参数

为什么它不被允许作为变量声明可能有争议(除其他外,它与float f[] = { ... }初始化数组声明语法不一致,但最终结果是函数参数decalaration引入了一个增加另一层混淆的符号。

例如,着名的argv参数可以以任何奇怪的方式声明:

 int main (int argc, char ** argv) int main (int argc, char * argv[]) int main (int argc, char argv[][]) 

另一方面,如果您完全了解指针和数组之间的区别,则空括号比指针表示法更方便,尤其是在这种情况下:

 void fun (float f[][10]); // pointer to arrays of 10 floats 

等效指针语法强制您使用括号:

 void fun (float (* f)[10]); 

在声明这样的变量时你无法避免:

 float (* f)[10]; // pointer to array of 10 floats float f[][10]; // <-- compiler error : array dimension not known 

结论

就函数原型而言,您可以在语法变体之间进行选择,但如果传递给函数的变量与原型不匹配,那么它们都将以泪流满面。

 float ** var1; // pointer to pointer to float float * var2[10]; // array of pointers to float float (* var3)[10]; // pointer to array of floats (mind the brackets!) float var4[10][10]; // array of arrays of floats (2d array of floats) // using empty brackets notation void fun1 (float f[ ][ ]); void fun2 (float f[10][ ]); void fun3 (float f[ ][10]); void fun4 (float f[10][10]); // using same syntax as for variables declaration void fun1 (float ** f); void fun2 (float * f[10]); void fun3 (float (* f)[10]); // <-- [] notation (arguably) easier to read void fun4 (float f[10][10]); // must always use square brackets in that case // even more choice for multiple level pointers void fun1 (float * f[]); // any funI (varJ) call with I != J will end up in tears 

最后的建议

这当然是个人品味的问题,但我建议使用typedef作为一种获得更多抽象的方法,并将C语法奇怪的使用限制到最小。

 // type definition typedef float (* tWeirdElement)[10]; typedef tWeirdElement (* tWeirdo)[10]; // pointer to arrays of 10 pointers // to arrays of 10 floats // variable declaration tWeirdo weirdo; // parameter declaration void do_some_weird_things (tWeirdo weirdo); 

首先清除你对数组和指针的困惑。 永远记住, 数组不是指针
在所有声明中,只有两个声明

 int array[NROWS][NCOLUMNS]; int (*array4)[NCOLUMNS]; 

是数组。 其余的是指针,而不是数组。

array1array2不是一个指针数组?

没有永不。 array1array2int **类型,即,是指向整数的指针的类型指针

所以当你把它们传递给f3 ,所有的指针都会被传递掉?

不,我在上面解释说array1array2不是指针数组。

关于array2传递给f2f2在forms参数中有一个普通指针,但是array2是一个指针数组,那么在将指针数组传递给f2时,如何访问各个行和列?

f2期望指向int的指针作为其第一个参数。 *array2是一个指向int的指针。 因此在电话中

 f2(*array2, nrows, ncolumns); 

指向int的指针作为第一个参数传递给f2 ,而不是指针数组。

当传递array4时,你将如何访问单个行和列? array4是一系列1D数组的指针,用于函数f2

由于您将指向int类型参数的指针传递给f2 ,因此您只能访问行。