【问题标题】:Functions with multi-dimensional arrays as formal parameters以多维数组为形式参数的函数
【发布时间】:2021-11-13 21:36:27
【问题描述】:

我试图弄清楚为什么以多维数组作为形式参数的函数的签名具有未调整大小的第一个维度,而其他维度则没有。其实上述陈述第二部分的答案很明确:不传递维数信息,编译器将不知道多维数组在内存中是如何组织的。 困扰我的是不一致。

为什么不要求明确指定所有维度(包括第一个维度)?

我想出了一个理论,我想看看它是否正确。

数组最常见的用法是一维数组。鉴于数组名称是指向数组第一个元素的指针,Dennis Ritchie 希望一维数组具有完全相同的签名,无论是使用数组语法还是使用指针语法。

使用指针语法,如果不指定大小信息,就不可能知道函数中要处理多少个元素。因此,丹尼斯·里奇(Dennis Ritchie)也通过使数组未调整大小(甚至让编译器完全忽略第一个维度的大小信息,如果提供的话)来强制对数组语法进行相同的签名。换句话说,您将有一个形式参数是指针或未调整大小的数组,而第二个形式参数是数组大小。

【问题讨论】:

  • K&R "C" 应该提供接近硬件的低级访问权限。所以语法确实显示了事物的“如何”。你应该知道你在哪里做什么。如今,它更多地是关于抽象,我们已经有了像 std::vector、std::array 或我们自己的多维数组包装器,它们构建在这些较低级别的结构之上。
  • “使用指针语法不可能知道函数中有多少元素要处理” 显然这不适用于空终止的 Cstrings。
  • 在 C++ 中,如果您不让数组衰减为指针,您可以 要求明确指定所有维度。顺便说一句,你读过What is array to pointer decay?
  • @Sandeep 这只是一个约定 :)
  • 几点观察:(1) 在 K&R C 中,您不能指定可变尺寸大小。如果您指定了大小,它必须是一个常数。 (2) 你并不总是知道大小。例如,考虑strlen。这需要一个char *,相当于char []。您在调用时不知道大小,因此要求它没有意义。

标签: arrays c multidimensional-array implicit-conversion


【解决方案1】:

在C语言中不能按值传递数组,只能传递一个指针,然后在函数中,从指针中索引来访问数组的内容。

因此;在函数声明中,如果您指定具有数组类型的参数,则在进行任何进一步分析之前,该参数将调整为指针类型。例如:

  • 你写:void f( int x[][5] );
  • 编译器看到:void f( int (*x)[5] );

如果第一种形式被禁止,该语言将具有同样的功能,即您必须使用指针版本定义参数。它只是语法糖——事实证明,这会给新手带来很多困惑,但那是另一回事了。

所以要回答你的问题——必须指定调整后的所有数组维度,否则编译器在通过传递的指针参数访问数组时不知道如何索引内存。现在应该清楚的是,虚假的第一个“维度”是无关紧要的,因为它是立即丢弃的语法糖。

【讨论】:

  • It is just syntactic sugar 你认为允许这种语法(int f()[5];void f(int [5]);复制整个数组)并使数组成为左值(int a[5]; int b[5]; a = b;)吗?破坏语言的任何部分?我的意思是,它不会破坏现有程序,因为语法是无效的。
  • @KamilCuk:至少在 C++ 中,使用模板,这种更改可能会改变现有程序的含义(可能是不好的例子,但template<typename T> std::size_t foo(T) { return sizeof(T); },你目前有指针的大小,你建议的大小为数组代替)。
【解决方案2】:

当您传递一个数组时,它会衰减为指向第一个元素的指针。这是为了方便。没有它,将字符串文字传递给这样的函数:

void foo(const char* str) {}

会很麻烦:

foo("Hello world");     // the const char[12] decays into ...
foo(&"Hello world"[0]); // what would have to be written like this without the decay

如果您想指定所有维度,只需获取数组的地址,您将获得一个指向 single 元素的指针 - 指定所有维度。

示例。这个函数接受一个指向int[2][10]的指针:

void f2d2(int (*)[2][10]) {}
int a2d[2][10];
f2d2(&a2d);      // and you call it like this

在 C++ 中,您可以通过引用数组来防止数组衰减为指向第一个元素的指针。

void f2d2(int (&)[2][10]) {}

int a2d[2][10];
f2d2(a2d);      // no decay

【讨论】:

  • 这不太方便,指向数组的指针足以避免复制。真正的原因是 C 设计者希望尽可能多地重用用 B 编程语言(C 的前身)编写的代码。 B 中没有数组,而有指针。于是他们发明了这种“数组衰减”技巧,给第三代程序员带来了困惑。
  • @tstanisl 您仍然有指向带有&arr 的数组的指针。方便的部分是在函数可以处理不同范围的数组而无需执行&arr[0] 时获取指向第一个元素的指针。不过,该选项仍然有效,就像 foo(&"Hello world"[0]); 一样。
  • &"Hello world"[0] 中有一个衰减,"Hello world"char[12](C++ 中的 const),它衰减char* 被 @ 取消引用987654335@ 使 char& 变为 char*
  • @tstanisl 我想说的是 without 衰减该语法将是获取指向第一个元素的指针的方法 - 因为指向第一个元素的指针元素很常见,有一个更短的方法可以“免费获得它”。
【解决方案3】:

C 标准中有两处引用使数组的使用更加清晰。

第一个是(6.3.2.1 左值、数组和函数指示符)

3 除非它是 sizeof 运算符的操作数或一元 & 运算符,或者是用于初始化数组的字符串文字, 具有“类型数组”类型的表达式被转换为 类型为“pointer to type”的表达式,指向初始 数组对象的元素并且不是左值。如果数组对象 有注册存储类,行为未定义。

第二个是(6.7.6.3 函数声明器(包括原型))

7 应调整参数声明为“类型数组” to ‘‘qualified pointer to type’’,类型限定符(如果有的话) 是在数组类型派生的 [ 和 ] 中指定的那些。 如果关键字 static 也出现在数组的 [ 和 ] 中 类型推导,然后对于函数的每次调用, 相应的实际参数应提供对第一个的访问 数组的元素至少与指定的元素一样多 大小表达式。

相对于函数声明,这意味着什么?

如果你声明了一个类似例子的函数

void f( int a[100] );

那么编译器会按照如下方式调整函数参数

void f( int *a );

例如,这些函数声明是等价的

void f( int a[100] );
void f( int a[10] );
void f( int a[1] );
void f( int a[] );
void f( int *a );

并声明相同的一个函数。你甚至可以在你的程序中包含所有这些声明,尽管编译器会发出一条消息,指出存在冗余声明。

在函数中,变量a 的指针类型为int *

另一方面,您可以调用传递不同大小数组的函数。数组指示符将由编译器隐式转换为指向其第一个元素的指针。您甚至可以通过指向它的指针传递一个标量对象。

所以这些函数的调用都是正确的

int a[100];
f( a );

int a[10];
f( a );

int a[1];
f( a );

int a;
f( &a );

因此,该函数不知道将哪个数组用作参数。所以你需要声明第二个函数参数,它将指定传递数组的大小(如果函数不依赖于数组中存在的标记值)

例如

void f( int a[], size_t n );

如果你有这样的多维数组

T a[N1][N2][N3]...[Nn];

那么指向其第一个元素的指针将具有类型

T ( *a )[N2][N3]...[Nn];

所以一个函数声明为

void f( T a[N1][N2][N3]...[Nn] );

等价于

void f( T ( *a )[N2][N3]...[Nn] );

如果 N2、N3、..Nn 是整数常量表达式,则该数组不是可变长度数组。否则它是一个可变长度数组,并且函数可能被声明为(当声明 os 不是函数定义的一部分时)

void f( T ( *a )[*][*]...[*] );

对于这样的函数,还存在确定子数组大小的问题。所以你需要声明将指定大小的参数。

例如

void f( size_t n1, size_t n2, size_t n3, ..., size_t nn, T a[][n2][n3]...[nn] );

对于 C++,可变长度数组不是标准的 C++ 特性。您也可以将函数参数声明为具有引用类型。

例如

void f( int ( &a )[10] );

在这种情况下,函数内的 a 不是 a 指针。它表示一个数组,表达式sizeof( a ) 将产生整个数组的大小而不是指针的大小。

【讨论】:

  • array-like 参数和指针参数之间存在细微差别。指针可以指向不完整的类型。所以int foo(int (*x)[]) 有效,而int foo(int x[][]) 无效
  • @tstanisl 好话。问题是你不能取消引用这样的指针。
猜你喜欢
  • 2016-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多