为什么sizeof(x ++)不增加x?


505

这是在dev c ++窗口中编译的代码:

#include <stdio.h>

int main() {
    int x = 5;
    printf("%d and ", sizeof(x++)); // note 1
    printf("%d\n", x); // note 2
    return 0;
}

我预计x在执行注释1后将为6 。但是,输出为:

4 and 5

谁能解释为什么注释1x之后不增加?


37
我会注意到DevC ++使用的是一个非常旧的过时的编译器,您可能希望升级到更新的IDE,例如Codeblocks Eclipse或Visual Studio
Tom J Nowell,

4
++ x产生的结果与x ++,cygwin和gcc 3.4.4相同。
yehnan

6
@Tim Pletzcker因为第一个值是变量的大小,而不是变量本身。
Surfbutler 2011年

我的答案是由于与该答案合并而添加的,因此我的答案实际上显示了VLAs在其他方法都没有的情况下如何获得运行时评估。
Shafik Yaghmour 2014年

2
应该避免这种书写,因为这可能会导致不良的副作用。您的问题已经是其中之一。
user666412

Answers:


537

来自C99标准(重点是我的)

6.5.3.4/2

sizeof运算符产生其操作数的大小(以字节为单位),该操作数可以是表达式或类型的括号名称。大小由操作数的类型确定。结果是一个整数。如果操作数的类型是可变长度数组类型,则对操作数求值;否则,不评估操作数,结果为整数常量。


51
“如果操作数的类型是可变长度数组类型,则将对操作数求值”哇!我从没意识到
Kos

6
可变长度数组类型是什么意思?那是否意味着操作数是一个数组?在这种情况下,代码不是数组。你能帮我清理一下吗?
Neigyl R. Noval

37
可变长度数组是声明的数组,其大小在编译期间是未知的值,例如,如果您N从stdin和make中读取int array[N]。这是C99的功能之一,在C ++中不可用。
科斯

21
@LegendofCage,特别是这意味着可以通过某种方式sizeof(int[++x])(确实是一个非常糟糕的主意)++进行评估。
詹斯·古斯特

3
@Joe Wreschnig:它是由评估gccclang并在ideone.com/Pf7iF
JFS

190

sizeof是一个编译时运算符,因此在编译时sizeof及其操作数将被结果值替换。的操作数没有评估(除了当它是一个可变长度数组)在所有; 仅结果的类型很重要。

short func(short x) {  // this function never gets called !!
   printf("%d", x);    // this print never happens
   return x;
}

int main() {
   printf("%d", sizeof(func(3))); // all that matters to sizeof is the 
                                  // return type of the function.
   return 0;
}

输出:

2

因为short在我的机器上占用了2个字节。

将函数的返回类型更改为double

double func(short x) {
// rest all same

将给出8作为输出。


12
仅在某些时候-如果可能的话,这是编译时间。
马丁·贝克特

10
-1,因为这与公认的(和正确的)答案相矛盾,并且没有引用标准。
sam hocevar 2011年

1
使用字符串时,sizeof()运算符的编译时解析有一个很好的好处。如果您有一个初始化为带引号的字符串的字符串,而不是使用strlen(),则必须在运行时扫描包含该字符串的字符数组以查找空终止符,那么在编译时就知道sizeof(quoted_string),因此在运行时。这是一件小事,但是如果您在无数次循环中使用带引号的字符串,则性能会发生重大变化。
user2548100 2014年

如果您确实在一个循环中使用了它数百万次,那么将长度计算从循环中分解出来会不会更明智?我当然希望您的代码中没有数以百万计的不同硬编码常量。:-o
Veky

47

sizeof(foo) 尽力在编译时发现表达式的大小:

6.5.3.4:

sizeof运算符产生其操作数的大小(以字节为单位),该操作数可以是表达式或类型的括号名称。大小由操作数的类型确定。结果是一个整数。如果操作数的类型是可变长度数组类型,则对操作数求值;否则,不评估操作数,结果为整数常量。

简而言之:可变长度数组,在运行时运行。(注意:可变长度数组是一个特定功能-不是分配给malloc(3)。的数组。)否则,仅计算表达式的类型,并在编译时进行计算。


33

sizeof是编译时内置的运算符,不是函数。在不用括号的情况下可以使用它,这一点变得非常清楚:

(sizeof x)  //this also works

1
但这如何回答这个问题?
塞巴斯蒂安·马赫

5
@phresnel:这只是为了清楚说明sizeof是“奇怪的”,并且不受常规函数规则的约束。无论如何,我还是编辑了该帖子,以消除与正常运行
时运

sizeof运算符不是编译时运算符,只需给它一个VLA即可解决。
paxdiablo

21

注意

此答案是从重复副本中合并而成的,这解释了延迟日期。

原版的

除了可变长度数组, sizeof不会评估其参数。我们可以从C99标准草案6.5.3.4 的sizeof运算符2段中看到这一点:

sizeof运算符产生其操作数的大小(以字节为单位),该操作数可以是表达式或类型的括号名称。大小由操作数的类型确定。结果是一个整数。如果操作数的类型是可变长度数组类型,则对操作数求值;否则,不评估操作数,结果为整数常量。

一个注释(现已删除)询问这样的事情是否会在运行时评估:

sizeof( char[x++]  ) ;

确实可以,像这样的东西也可以工作(看到他们两个都活着):

sizeof( char[func()]  ) ;

因为它们都是可变长度数组。虽然,我都没有发现任何实际用途。

注意,可变长度数组被覆盖在草案标准C996.7.5.2 组声明第4段

[...]如果大小是整数常量表达式,并且元素类型具有已知的常量大小,则数组类型不是可变长度数组类型;否则,数组类型为可变长度数组类型。

更新资料

在C11中,答案针对VLA情况而改变,在某些情况下,不确定是否评估大小表达式。从“ 6.7.6.2 数组声明器”部分说:

在size表达式是sizeof运算符的操作数的一部分并且更改size表达式的值不会影响该运算符的结果的情况下,不确定是否评估size表达式。

例如,在这种情况下(现场观看):

sizeof( int (*)[x++] )

1
这里要记住的重要一点是,在大多数情况下,sizeof它实际上是一个宏-它不创建代码,而是预先计算期望值并将其直接存入代码中。请注意,这是直到C99 的唯一行为,因为VBA并不存在(直到得到这个答案之前,我才真正听说过它们,信不信由你!)
Corley Brigman 2014年

除了确定表达式的值和for的新值以外sizeof (char[x++]);,将使用值的x其他方式做什么?x++x
supercat 2014年

@alk ...嗯,是的,我的意思是“ VLA” :) Shafik-为什么那些在运行时求值?就像我说的那样,我从未见过VLA,但是它们的类型在编译时都是已知的,不是吗?
Corley Brigman 2014年

@CorleyBrigman,标准的回答是b / c,但是原因是b / c我们在编译时不知道数组大小,因此我们必须在运行时评估表达式。VLA是一个有趣的话题,这是我在这里这里发表的两篇文章。
Shafik Yaghmour 2014年

@Shafik-啊,好吧,我想我的困惑是我没有意识到这char[x++]是VLA。char*在我不熟悉的眼睛看来,它实际上就像是一个。
Corley Brigman

11

由于sizeof未评估运算符的操作数,因此可以执行以下操作:

int f(); //no definition, which means we cannot call it

int main(void) {
        printf("%d", sizeof(f()) );  //no linker error
        return 0;
}

在线演示:http : //ideone.com/S8e2Y

也就是说,f如果sizeof仅使用该函数,则无需定义该函数。该技术主要用于C ++模板元编程中,因为即使在C ++中,sizeof也不会评估操作数。

为什么这样做?之所以起作用,是因为sizeof运算符不对value进行运算,而是对表达式的类型进行运算。因此,在编写时sizeof(f()),它对表达式的类型进行操作f(),而这仅是函数的返回类型f。无论函数实际执行返回什么值,返回类型始终相同。

在C ++中,您甚至可以:

struct A
{
  A(); //no definition, which means we cannot create instance!
  int f(); //no definition, which means we cannot call it
};

int main() {
        std::cout << sizeof(A().f())<< std::endl;
        return 0;
}

然而,在中sizeof,我似乎首先是A通过编写来创建的实例,A()然后f通过编写在实例上调用该函数A().f(),但是没有发生这种情况。

演示:http : //ideone.com/egPMi

这是另一个主题,解释了的其他一些有趣的属性sizeof


10

在编译期间无法执行。所以++i/ i++不会发生。也sizeof(foo())将不执行功能,但返回正确的类型。


2
编译期间无法执行。 ”是什么意思?
curiousguy

1
编译只会创建目标代码...仅当用户执行二进制文件时,才会执行目标代码。由于sizeof发生在编译时,假设i ++将递增是错误的。
rakesh

因为sizeof发生在编译时 ”是指:“就像sizeof编译时常量表达式一样”吗?
curiousguy

就像在预处理过程中发生“ #define”一样,sizeof也将在编译时发生。在编译期间,所有类型信息都是可用的,因此可以在编译期间评估sizeof并替换值。正如@pmg之前提到的“来自C99标准”。
rakesh

1
对于非可变长度数组,“ sizeof将在编译时发生
curiousguy 2011年

0

sizeof在编译时运行,但x++只能在运行时评估。为了解决这个问题,C ++标准规定sizeof不对操作数进行评估。C标准说:

如果[of sizeof] 的操作数类型是可变长度数组类型,则对操作数求值;否则,不评估操作数,结果为整数常量。


C ++中没有VLA。
LF

-2

sizeof() 运算符仅给出数据类型的大小,它不评估内部元素。


这是错误的,sizeof()操作符是递归操作的,并且将获得容器中所有元素,类或结构的成员等的大小(以字节为单位)。您可以通过创建一个包含几个成员的简单类并轻松地向自己证明这一点。呼吁sizeof()。(但是,其中的任何指针指针都看不到-的大小。)这一切都是在编译时发生的,正如其他注释者所指出的那样:sizeof()不会对表达式内部的值求值。
Tyler Shellberg
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.