sizeof如何知道操作数数组的大小?

36

这可能是一个愚蠢的问题,但是当您没有传递数组中元素的数量时,sizeof 运算符如何知道数组操作数的大小。我知道它不返回数组中的总元素数,而是以字节为单位的大小,但是它仍然必须知道数组何时结束才能得到这个结果。只是好奇这是如何工作的。


7
+1 给所有说 sizeof 是编译时运算符的人,-1 给所有说它在运行时跟踪指针所指向的数组大小的人。 - Billy ONeal
6
我希望你能翻译这句话:“我想点赞所有那些说它也是一个编译时运算符的答案。但可悲的是,他们没有考虑到这个标记为“c”的问题,应该详细说明C中的VLA,其中sizeof需要评估其参数并产生一个运行时值以获得大小。因此,我为C++部分+1,为回答问题的C部分-1,总共给出0个赞。”请注意保持原意和尽量通俗易懂。我赞同那些说sizeof也是编译时运算符的答案。但不幸的是,这个问题还涉及到C语言的VLAs,其中sizeof需要在运行时计算其参数的大小。所以对于C++部分我点赞,而对于C的回答我则不予点赞,结果是我一共没有给出任何赞。 - Johannes Schaub - litb
1
@Johannes:虽然这是真的,但绝大多数的C代码仍然是C89/C90代码,其中不允许使用可变长度数组(VLAs)。 - Billy ONeal
3
@Billy 即使是这样,也不会否定我的观点。但我表示怀疑。混合代码/声明仍然是C99的一部分,仍有很多人这样做,并且经常看到人们在运行时使用i来写入 int a[i],甚至没有考虑C99 - 他们认为这是老派的C语言。鉴于问题,我甚至不确定 @marchinram 是否知道区别,因此向他解释更加重要。 - Johannes Schaub - litb
这实际上是一个很好的问题,它让我们更深入地了解C/C++的工作原理。谢谢大家。 - Galaxy
显示剩余4条评论
12个回答

50

sizeof 在编译时被解释,编译器知道数组如何声明(因此知道所需的空间大小)。在动态分配的数组上调用 sizeof 可能不会产生您想要的结果,因为(正如您提到的那样)数组的结束点未指定。


根据问题本身的讨论以及阅读规范,现代C语言至少是不正确的。最好将此答案删除。http://en.wikipedia.org/wiki/Sizeof - xaxxon
2
@xaxxon:标准并没有明确说明它是在编译时评估的,但它确实说sizeof不会评估其操作数。操作数表达式只对编译器有意义,因此这真的是唯一可以实现它的地方。可变长度数组由编译器间接处理。编译器已经必须生成代码来计算为VLA分配多少空间(数组成员大小*某个变量),因此它可以重用该数字以用于对该VLA进行任何sizeof调用。 - bta

22
你难以理解这个问题的根本原因可能是你混淆了数组和指针,就像很多人一样。但是,请注意,数组并不是指针。例如,double da[10]是由10个double组成的数组,而不是double*,当你要求计算sizeof(da)时,编译器肯定知道这一点。你不会对编译器知道sizeof(double)感到惊讶吧?
数组的问题在于,在许多情况下(例如当它们被传递给函数时),它们会自动衰减为指向其第一个元素的指针。但是请注意,数组仍然是数组,指针仍然是指针。

2
+1 是为了解释这里常见误解的根源。 - Billy ONeal
绝对正确,sbi。Peter van der Linden的杰出著作《Expert C Programming》的第4、9和10章在区分指针和数组方面做得非常好。实际上,第4章的标题就是“令人震惊的真相:C数组和指针并不相同”,正如sbi所说的那样。该书专门花费了15页来讲解这个主题。请查看目录,特别是第4、9和10章:http://books.google.com/books?id=9t5uxP9xHpwC&lpg=PP1&dq=expert%20c%20programming&pg=PR8#v=onepage&q&f=false - Dan
1
所以你的意思是,“编译器知道sizeof(double)的大小,这并不让你感到惊讶?”因为编译器看到了声明并将其与适当的数据类型匹配。所以编译器看到了声明double da[10],然后在代码中稍后出现sizeof(da)时,编译器会说:“啊哈,我知道da是一个由10个double组成的数组,因为我可以看到声明在第8行列出了数组的大小。”因此,编译器在编译源代码时从源代码本身推断出数组的大小。我明白了,谢谢! - Galaxy
@sbi 我的问题是在while循环内部使用a*b+csizeof(arr)fgets中是否最优,现在我知道了。谢谢。 - prometeu

13
除了一个例外,sizeof在编译时完成它的工作。在编译时,编译器跟踪对象的完整类型[编辑:无论如何,如果类型不完整以至于大小没有包括在内,尝试使用sizeof将失败],sizeof基本上只是将编译器中的一部分信息“导出”到正在编译的代码中,因此在生成的代码中成为常量。
异常情况是应用于可变长度数组(VLA)时的sizeof。当应用于VLA时,sizeof评估其操作数(否则它不会),并产生VLA的实际大小。在这种情况下,结果不是一个常量。
1. VLAs 在C99中正式成为C的一部分,但是一些编译器在此之前支持它们。虽然VLAs不是C++的官方组成部分,但某些编译器(例如g ++)也将VLAs作为对C++的扩展。

3
编译器会跟踪完整类型……除非类型是不完整的,那样的话就不能使用 sizeof - Potatoswatter
这是不正确的。变长数组 sizeof 调用并非在编译时处理。您应该更新您的答案或将其删除。http://en.wikipedia.org/wiki/Sizeof - xaxxon

9
编译器知道您应用程序中每种类型的大小,sizeof只是请求编译器为您生成该值。

这是不正确的。变长数组 sizeof 调用并非在编译时处理。您应该更新您的答案或将其删除。http://en.wikipedia.org/wiki/Sizeof - xaxxon
@xaxxon:目前,在C++中,变长数组是无效的,因此答案仍然是:所有有效的C++类型的大小在编译时计算。变长数组在上次会议上向委员会提出了建议,并得到了良好的反响,但在下一个标准(C++14)获得批准之前,这不会成为有效的C++。即使有了VLAs,编译器也会知道大小(它知道创建数组的表达式,因此可以将数字保存在一旁),由于VLAs不能逃离函数的范围,编译器可以在需要时注入相同的值。 - David Rodríguez - dribeas

8

Sizeof 是一个编译时操作符,它拥有与编译器相同的信息(显然编译器知道数组的大小)。

这就是为什么如果你对指针使用 sizeof,你会得到指针的宽度,而不是指向该指针的数组的大小。


那么,(几年没有使用C++了),如果您请求sizeof并取消引用指向数组的指针,会发生什么? sizeof是否会失败,还是它会查找有关数组大小的记录并返回它? - John Fisher
2
如果您取消引用指针,则会获得对数组存储的对象的引用,并且sizeof将返回该引用的大小。 int a [5]; int * p = a; assert(sizeof(* p)== sizeof(int); - Dennis Zickefoose
这是不正确的。变长数组 sizeof 调用并非在编译时处理。您应该更新您的答案或将其删除。http://en.wikipedia.org/wiki/Sizeof - xaxxon
@xaxxon:在这里,我们遇到了语言不一致的问题。在C++14中,尽管添加了堆栈分配的可变长度内置数组,它仍然是一个编译时操作符。在C99中,它不是编译时的,但我认为使用情况并不多,因为它在C11中是可选的,并且一个主要编译器完全没有支持它。对于一个初学者来说,我不认为有任何理由为一个很少使用的语言特性增加答案的复杂度,因为这种区别对所提出的问题没有影响。还有,在我三年前写这篇回答时。 - Billy ONeal
是的,我们需要阻止人们将东西标记为C和C++,因为这意味着你不知道发生了什么。 - xaxxon

4

Sizeof 只能应用于完全定义的类型。编译器将能够在编译时确定大小(例如,如果您有像 int foo [8]; 这样的声明),或者它将能够确定它必须添加代码来跟踪变量长度数组的大小(例如,如果您有像 int foo [n + 3]; 这样的声明)。

与此处其他答案相反,请注意自 C99 开始,sizeof() 不一定在编译时确定,因为数组可能是可变长度的。


谈论自我实现的预言 :) - ninjalj
这是不正确的。变长数组 sizeof 调用在编译时无法处理。您应该更新您的答案或将其删除。http://en.wikipedia.org/wiki/Sizeof - xaxxon
@xaxxon:而且您不应该在三年前发布的问题的每个答案中都垃圾评论。特别是当有关可变长度数组(VLAs)的讨论已经在问题本身的评论中进行了三年。 - Billy ONeal

2

如果您在本地变量上使用sizeof,它知道您声明了多少个元素。如果您在函数参数上使用sizeof,它就不知道了;它将参数视为指向数组的指针,并且sizeof会给出指针的大小。


它确实知道。参数指针(指向第一个元素,而不是数组),编译器知道指针的大小。C语言没有数组参数。 - Keith Thompson

1

sizeof 通常在编译时计算。值得注意的例外是 C99 的可变长度数组。

int main(int argc, char **argv)
{
    if (argc > 1)
    {
        int count = atoi(argv[1]);
        int someArray[count];

        printf("The size is %zu bytes\n", sizeof someArray);
    }
    else puts("No");
}

1

来自维基百科的引用:

实现sizeof运算符是编译器作者的责任,必须针对特定的语言实现以正确的方式进行。sizeof运算符必须考虑底层内存分配方案的实现,以获取各种数据类型的大小。sizeof通常是一个编译时运算符,这意味着在编译期间,sizeof及其操作数将被替换为结果值。这在C或C++编译器生成的汇编语言代码中很明显。因此,尽管它的使用有时看起来像函数调用,但sizeof仍然是一个运算符。


2
虽然这是正确的,但它并没有真正回答OP的问题。 - Billy ONeal

1

sizeof operator会“知道”所有基本数据类型的大小,因为结构体、联合和数组只能通过组装基本类型来构建,所以很容易确定任何类型的数组的大小。它使用基本算术运算来确定复杂类型(在编译时)。


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