在这种情况下,C语言是否正确处理了sizeof(...)和sizeof...?

38

在下面的代码中,函数testtest2是否等价?

typedef int rofl;

void test(void) {
    rofl * rofl = malloc(sizeof(rofl)); // Is the final rofl here the TYPE?
}

void test2(void) {
    rofl * rofl = malloc(sizeof *rofl); // Is the final rofl here the VARIABLE?
}

换句话说:

  1. sizeof(rofl)中的rofl是否因为括号选择了rofl类型?
  2. sizeof *rofl中的rofl是否因为缺少括号正确选择了rofl变量?

注意:这是一个看起来很傻的例子,但实际上在实践中可能会出现类型名称与变量名称相同的情况。因此有这个问题。


8
为什么人们投票反对这个问题?这是一个完全合理的问题,鉴于C语言确实具有多个命名空间,因此某些名称可能具有多个可见含义,所以除非您特别知道typedef名称和对象名称共享相同的名称空间并且可以相互隐藏,否则您无法明确回答这个问题。 - Eric Postpischil
2
简短的回答是不需要,括号没有任何区别。sizeof是一个运算符而不是函数,所以括号只是多余的。 - user207421
2
顺便回答你的第二个问题,是的,缺少括号会强制 sizeof 操作数只有一个选项:它必须是某种“一元表达式”,因此不能是 typedef 名称。关于第一个问题,括号并不会导致编译器需要 typedef 名称。如果括号中只是一个标识符,则在该点上只能有效地是一个 typedef、函数或对象的名称,因此编译器只会找到一个解决方案;它不需要选择。 - Eric Postpischil
4
没有矛盾。当存在括号时,有两种语法选项;当不存在括号时,只有一种语法选项。用圆括号括起来的标识符 sizeof (foo) 可以匹配 C 标准第 6.5.3 条款中所示的 “sizeof unary-expression” 或同一条款中的 “sizeof ( type-name )”。然后,匹配取决于标识符的含义,而不是局部语法。如果没有括号,只有第一个选项可能。 - Eric Postpischil
最好从不那么复杂的示例开始,以便(分别)了解sizeof和作用域是如何工作的。 - Bernhard Barker
显示剩余7条评论
3个回答

27

在这两种情况下,最后一个rofl是变量名。变量名出现时就会处于作用域中;在当前作用域的其余部分中,在普通上下文(*)中,该标识符始终表示变量名。

sizeof操作符不会引入任何特殊的名称查找情况。实际上,没有语言构造会使用标识符的隐藏含义。

实践中,不建议将类型名称和变量名称使用相同的标识符。


(*) 标签名称、结构体标记和结构体成员是三个标识符的特殊上下文环境。但在所有其他上下文环境中,所有标识符共享一个公共名称空间:不存在用于区分类型名称、变量名称、函数名称等的不同标识符名称空间。

这里有个刻意制造的例子:

typedef int A;      // "A" declared as ordinary identifier, meaning a type name

struct A { A A; };  // "A" declared as struct tag and member name -- OK as these are three different name spaces. Member type is "int"

A main()            // int main() - ordinary context
{
    struct A A();   // "A" declared as ordinary identifier, meaning a function name; hides line 1's A
    // A C;         // Would be error: ordinary A is a function now, not a typedef for int
    struct A B;     // OK, struct tags have separate name space
    A:+A().A;       // OK, labels and struct members have separate name space, calls function
    goto A;         // OK, label name space
}

你应该明确解释rolf作为一个对象的标识符(名称)隐藏了typedef名称的rolf作为标识符。这并不清楚,因为C具有多个命名空间。例如,它作为标签的使用不会隐藏对象的使用。一个人可能会认为“sizeof(rofl)”可以解析为typedef名称的大小或对象的大小;如果它们在不同的命名空间中,它们都可以可见。编译器如何选择?但是,如果我们解释说typedef名称由于在相同的命名空间中而不可见,那么它的解析方式就很清楚了。 - Eric Postpischil
顺便提一下,C标准使用“隐藏”而不是“遮蔽”。 - Eric Postpischil
@EricPostpischil 关于您的第一条评论,我的文本“对于当前作用域的剩余部分,该标识符始终表示变量名”已经传达了这个意思?我甚至在第二段中尝试加强了这一点,表明隐藏的含义从未被使用。 - M.M
1
“那个标识符总是表示变量名”这个说法实际上并不正确。标识符可以同时是对象的名称、标签的名称、多个结构体或联合体成员的名称以及结构体、联合体或枚举的标记。这些可能性是通过语法上下文来区分的。因此,这个答案实际上没有解释C名称空间在问题的上下文中是如何工作的——OP的问题本质上是询问sizeof的两种语法形式是否将类型名称与对象名称区分开来... - Eric Postpischil
因此,我认为澄清一下可能会有所帮助,尽管C语言对于标识符有多个命名空间,但typedef名称和对象名称与函数名称共享一个命名空间(不同于标签),因此其中一个可以隐藏另一个。 - Eric Postpischil
@EricPostpischil,我添加了更多的解释,感谢您指出这个问题。 - M.M

13

在这个声明中

rofl * rofl = malloc(sizeof(rofl)); // Is the final rofl here the TYPE?

变量名rofl掩盖了typedef名rofl。因此在sizeof运算符中使用了指针rofl,即表达式的类型为int *

对于该声明也是一样的。

rofl * rofl = malloc(sizeof *rofl); 

除了使用具有类型为int的typedef名称rofl的解除引用指针rofl表达式之外,似乎存在某些混淆。

这似乎是由于C语法定义引起的。

sizeof unary-expression
sizeof ( type-name )

然而,unary-expression可以是一个被圆括号包含的基本表达式。

引自C标准(6.5.1 基本表达式)。

primary-expression:
    ( expression )
    //...

例如,如果x是一个变量的名称,那么您可以写成下面两种方式之一:

sizeof x 
或者
sizeof( x )

为了清晰明了起见,您可以在sizeof运算符和主表达式之间插入空格。

sizeof    ( x )
operator  primary expression

假设我们要进行比较,考虑另一个一元运算符:正号。例如,你可以这样写:

+ x
或者
+ ( x )

现在只需将一元加号替换为另一个一元运算符sizeof

至于隐藏名称的问题,对于结构、联合和枚举来说是可以解决的,因为它们的名称中包含用于标记的关键字。

例如:

typedef struct rofl { int x; } rofl;

void test(void) {
    rofl * rofl = malloc(sizeof( struct rofl));
}

在使用 sizeof 运算符的这个函数中,使用了类型名称为 struct rofl 的结构体。

而在这个函数中

typedef struct rofl { int x; } rofl;

void test(void) {
    rofl * rofl = malloc(sizeof( rofl));
}

使用 sizeof 运算符时,需要使用变量 rofl 的原始表达式,该变量的类型为 struct rofl *


2

这里没有“选择”或“挑选”的过程。在两种情况下,每个sizeof调用中引用的rofl都是变量,而不是类型,由于作用域规则。该变量在内部作用域中声明,因此覆盖了类型名称。对sizeof运算符的参数进行括号化是无关紧要的。

祝好运。


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