数组指针如何存储其大小?

5
#include "stdio.h"

#define COUNT(a) (sizeof(a) / sizeof(*(a)))

void test(int b[]) {
  printf("2, count:%d\n", COUNT(b));
}

int main(void) {
  int a[] = { 1,2,3 };

  printf("1, count:%d\n", COUNT(a));
  test(a);

  return 0;
}

结果是显而易见的:
1, count:3
2, count:1

我的问题:

  1. 当“a”被声明时,长度(计数/大小)信息存储在哪里?
  2. 为什么将“a”传递给test()函数时会丢失长度(计数/大小)信息?

1
C标准规定了“#include <stdio.h>”,你应该遵循标准保持一致性,除非你真的有足够好的理由不这样做(但是,如果你足够了解符号的使用,你就不需要问这个问题)。 - Jonathan Leffler
6个回答

8
在C语言中,不存在“数组指针”的概念。数组的大小没有存储在任何地方。变量a不是指针,而是int[3]类型的对象,编译器在编译时已经知道这个事实。因此,当你要求编译器计算sizeof(a) / sizeof(*a)时,编译器知道答案是3。当你将a传递给函数时,你故意让编译器将数组类型转换为指针类型(因为你声明了函数参数为指针)。对于指针,你的sizeof表达式会产生完全不同的结果。

谢谢!我现在明白了。 - Lion
+1 表示 a 是一个类型为 int[3] 的对象,尽管我不一定同意他声明函数参数为指针的说法,因为他使用了 int b[] 而不是 int *b。只是标准规定作为参数传递的数组会衰变为指向第一个元素的指针。 - SiegeX
2
@SiegeX:标准明确规定参数声明中的 int b[] 等同于 int *b。这不是“数组类型衰减”的实例。参数可以“衰减”,但参数声明受标准中另一个独立部分描述的不同“过程”所覆盖。 - AnT stands with Russia
C99(ISO/IEC 9899:1999)的§6.7.5.3/6和/7规定:“6个参数类型列表指定函数参数的类型,并可能声明标识符。 7将参数声明为“类型数组”的声明应调整为“类型限定符指针”。 - Jonathan Leffler
1
再次强调,“数组类型衰减”并没有一个精确定义的术语。当然,参数声明和实际数组的处理有很多相似之处。但是,从语义上讲,这两个过程是显著不同的。从语义上讲,实际数组对象衰减为指针是一回事,而数组参数声明被替换为指针声明则完全是另一回事。你可以称两者都为“数组类型衰减”,没错。但在这种区别很重要的情况下,混淆两者将是一个错误。 - AnT stands with Russia
显示剩余2条评论

5
  1. 当“a”被声明时,长度(count/size)信息存储在哪里?

它没有存储在任何地方。当真正的数组作为操作数传递给sizeof运算符(如第一个printf()中所使用的那样)时,sizeof运算符(用于COUNT()宏中)返回整个数组的大小。

  1. 为什么将“a”传递给test()函数时,长度(count/size)信息会丢失?

不幸的是,在C中,将数组参数传递给函数是虚构的。数组不会传递到函数中;参数被视为指针,并且在函数调用中使用的数组参数被“衰减”为简单的指针。 sizeof运算符返回指针的大小,这与用作参数的数组的大小没有关系。

顺便说一句,在C++中,您可以将函数参数作为对数组的引用,在这种情况下,完整的数组类型可用于函数(即,参数不会衰减为指针,sizeof将返回完整数组的大小)。但是,在这种情况下,参数必须与数组类型完全匹配(包括元素的数量),这使得该技术在模板中才有用。

例如,以下C++程序将执行您所期望的操作:

#include "stdio.h"

#define COUNT(a) (sizeof(a) / sizeof(*(a)))

template <int T>
void test(int (&b)[T]) {
  printf("2, count:%d\n", COUNT(b));
}

int main(int argc, char *argv[]) {
  int a[] = { 1,2,3 };

  printf("1, count:%d\n", COUNT(a));
  test(a);

  return 0;
}

1
谢谢。您的回答让我更清楚了。那么最后一个问题是:&(a[0])和a这两个指针有什么区别? - Lion
1
严格来说,a 不是一个指针 - 它是一个数组。然而,在几乎所有的表达式中(一个例外是当它是 sizeof 的参数时),它会“衰变”成一个指针,该指针与 &(a[0]) 的值和类型完全相同。 - Michael Burr
谢谢你的回复!我现在明白了。 - Lion
1
你对问题(1)的回答部分不正确。如果数组是变长的,那么其长度被存储在某个地方,并且可以通过sizeof检索,但是它被存储在哪里是一项实现细节,最接近答案的东西是“在sizeof array中”。 - R.. GitHub STOP HELPING ICE
@R.. - 你说的关于可变长度数组(VLAs)的问题是正确的。我经常忘记它们,因为除了实验性使用外,我不会在处理的平台上使用它们(通常不支持)。我有时会想知道它们在现实世界中有多少实际用途? - Michael Burr
@Michael:希望不会太多。正如我在许多其他问题中所讨论的那样,几乎不可能以既安全又具有任何实际优势的方式使用VLA,而固定大小的自动数组则可以。我必须非常努力地发明一类递归算法,其中它们绝对有用。 - R.. GitHub STOP HELPING ICE

1
1. Where is the length(count/size) info stored when "a" is declared?

它没有被存储。编译器知道a的大小,因此可以用实际大小替换sizeof()

2. Why is the length(count/size) info lost when "a" is passed to the test() function?

在这种情况下,b 被声明为一个指针(即使它可能指向 a)。由于是指针,编译器不知道所指数据的大小。

1
  1. 没有。
  2. 因为一开始它就没有被存储。

当您在main()中引用数组时,实际的数组声明定义是可见的,因此sizeof(a)会给出以字节为单位的数组大小。

当您在函数中引用数组时,参数实际上是'void test(int *b)',指针大小除以它所指向的东西的大小在32位平台上恰好为1,在64位LP64架构(或者像Windows-64这样的LLP64平台)上则为2,因为指针是8个字节,而int是4个字节。

没有一种通用的方法来确定传递到函数中的数组的大小;您必须显式地手动传递它。


从评论中:

我还有两个问题:

  1. 你说的“实际声明可见”是什么意思?编译器(或操作系统)可以通过sizeof(a)函数获取长度信息吗?
  2. 为什么指针&(a[0])不像指针“a”一样包含长度信息?
我认为你在学习C之前学过Java,或者其他更现代的语言。归根结底,这是因为“C被定义的方式”。操作系统没有参与其中;这是一个纯编译器问题。sizeof()是一个运算符,而不是一个函数。除非你处理的是VLA(可变长度数组),它在编译时计算并且是一个常量值。在main()中,数组定义(当我说“声明”时我说错了)在那里,当将实际数组的名字应用于sizeof()运算符时(与函数的数组参数相反),则返回的大小是数组的字节大小。 因为这是C而不是Algol、Pascal、Java、C#……C没有存储数组的大小——这是不争的事实。当一个数组传递给一个函数时,大小信息不会传递到函数;数组“退化”为指向数组零元素的指针,只有该指针被传递。

谢谢。我还有两个问题:1、“实际声明可见”是什么意思?为什么编译器(或操作系统)可以通过sizeof(a)函数获取长度信息?2、为什么指针&(a[0])不像指针“a”一样包含长度信息? - Lion

0
1. 当“a”被声明时,长度(计数/大小)信息存储在哪里? 答:不存在。顺便说一句,这个问题没有意义。
2. 当“a”传递给test()函数时,为什么长度(计数/大小)信息会丢失? 答:当数组传递给函数时,它会退化为指向第一个元素的指针。因此,答案是“不存在”。和前面的问题一样,这个问题也没有任何意义。

0

数组指针不存储大小。然而,[]类型实际上不是指针,它是一种不同的类型。当你说int a[] = {1,2,3};时,你定义了一个由3个元素组成的数组,因为它被定义了,所以sizeof(a)会给出整个数组的大小。

然而,当你将参数声明为int a[]时,这与int *a基本相同,sizeof(a)将是指针的大小(这可能巧合地与int的大小相同,但并非总是如此)。

在C中,没有办法将大小存储在指针类型中,因此如果你需要大小,你必须将其作为附加参数传递或使用struct


"数组指针" - alk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接