Does C correctly handle sizeof(...) and sizeof ... in this case?
Asked Answered
S

3

38

In the following code, are the functions test and test2 equivalent?

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?
}

In other words:

  1. Does rofl in sizeof(rofl) correctly pick the rofl type because of the parentheses?
  2. Does rofl in sizeof *rofl correctly pick the rofl variable because of a lack of parentheses?

Note: This is a silly-looking example, but it can actually happen in practice that you have a type name that's the same as a variable name. Hence the question.

Swinge answered 15/11, 2017 at 1:15 Comment(12)
Why did people vote this down? It is a perfectly reasonable question, and the answer is not clear given that C does have multiple name spaces, so some names can have multiple visible meanings, so you cannot answer this question definitively unless you specifically know that typedef names and object names share the same name space and can hide each other.Coalition
The short answer is no, the parentheses make no difference. sizeof is an operator, not a function, so the parentheses are merely redundant.Katelyn
By the way, to answer your question 2 explicitly, yes, the lack of parentheses forces the sizeof operand to have only one option: It must be a “unary-expression” of some sort, so it cannot be a typedef name. In regard to question 1, the parentheses do not cause the compiler to require a typedef name. The thing in parentheses, if it is just an identifier, can only validly be the name of one typedef, function, or object at that point, so the compiler will only find one resolution; it does not need to choose.Coalition
@EricPostpischil You've contradicted yourself. Either the parentheses make a difference or they don't. unary-expression can be an identifier.Katelyn
@EJP: There is no contradiction. When parentheses are present, two syntactic options are possible. When parentheses are absent, there is only one syntactic option. With an identifier in parentheses, sizeof (foo) can match either the “sizeof unary-expression” shown in clause 6.5.3 of the C standard or the “sizeof ( type-name )” in the same clause. The match is then determined by which meaning the identifier has, not by local syntax. Without parentheses, only the first is possible.Coalition
Do parentheses make a difference when determining the size of an array? Why (and when) do I need to use parentheses after sizeof?Bermudez
It would've been a good idea to start with less convoluted examples just to (separately) see exactly how sizeof and scoping works.Bermudez
@EricPostpischil With over 46k rep, you should have seen the "Avoid answering questions in comments" many times by now. It just leads to long and convoluted comment threads, with no means to accept a correct answer or to downvote a bad one.Turanian
If you have type names which are the same as variable names, one needs to be changed.Tuberculous
@pipe: The questions I answered were not actually the main question posted but a rewording (“in other words”). As the main question had already been answered reasonably well, I judged that posting a different answer addressing technicalities raised by the alternate wording would be more of a distraction than a comment.Coalition
@pipe: If a 46k rep is defying it it calls the rule into question.Scherzo
Note that after the line shown in each function, there is no way to introduce another variable of type rofl in the same scope; the variable name hides the type name. Also note that if the typedef were in the function, the variable could not be defined in the same scope. (That's moderately carefully worded to allow a nested scope to (re)define the type or define variables of the same name as the type in the outer scope.)Lynnlynna
R
27

In both cases, the last rofl is the variable name. A variable name is in scope as soon as it appears; and for the remainder of the current scope, that identifier in an ordinary context(*) always means the variable name.

The sizeof operator does not introduce any special cases for name lookup. In fact, there are no language constructs that will use the hidden meaning of an identifier.

In practice it is a good idea to not use the same identifier for a type and a variable name.


(*) There are three special contexts for identifiers: label names, structure tags, and structure members. But in all other contexts, all identifiers share a common name space: there are no distinct identifier name spaces for type names versus variable names versus function names etc.

Here is a contrived example:

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
}
Ralaigh answered 15/11, 2017 at 1:18 Comment(6)
You should explain explicitly that rolf as an identifier (name) for an object hides rolf as an identifier for a typedef name. This is not clear because C does have multiple name spaces. Its use as a label, for example, would not hide the use for an object. A person could think “sizeof(rofl)” could parse as the size of the typedef name or the size of the object; they could both be visible of they are in separate name spaces. How does the compiler pick? But if we explain that the typedef name is not visible because of the hiding in the same name space, then it is clear how it parses.Coalition
BTW, the C standard uses “hide,” rather than “shadow.”Coalition
@EricPostpischil Re. your first comment, my text "for the remainder of the current scope, that identifier always means the variable name" does convey that already? I even tried to reinforce this in the second paragraph saying that the hidden meaning is never used.Ralaigh
Well, the statement “that identifier always means the variable name” is not actually true. An identifier can simultaneously be the name of an object, the name of a label, the name of several struct or union members, and a tag of a structure, union, or enumeration. These possibilities are distinguished by syntactic context. So this answer is not really explaining how C name spaces work in the context of the question—the OP’s question essentially asks whether the two syntactic forms of sizeof distinguish type names from object names…Coalition
… So I think it could be helpful to clarify that, although C has multiple name spaces for identifiers, typedef names and object names do share one name space (along with function names), so one can hide the other, unlike labels.Coalition
@EricPostpischil I added from further explanation, thanks for pointing out the issueRalaigh
K
13

In this declaration

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

the name of the variable rofl hides the typedef name rofl. Thus in the sizeof operator there is used the pointer rofl that is the expression has the type int *.

The same is valid for this declaration

rofl * rofl = malloc(sizeof *rofl); 

except that there is used an expression with the dereferenced pointer rofl that has the type of the typedef name rofl that is the type int.

It seems that the confusion arises due to this C grammar definition

sizeof unary-expression
sizeof ( type-name )

However unary-expression can be a primary expression that is an expression enclosed in parentheses.

From the C Standard (6.5.1 Primary expressions)

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

So for example if x is a name of a variable then you may write either

sizeof x 

or

sizeof( x )

For clarity you could insert blanks between the sizeof operator and the primary expression

sizeof    ( x )
operator  primary expression

For comparison consider another unary operator: the unary plus. You can write for example

+ x

or

+ ( x )

Now just substitute the unary plus for another unary operator sizeof.

As for hiding names the problem is resolvable for structures, unions and enumerations because their names include keywords for tags.

For example

typedef struct rofl { int x; } rofl;

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

In this function with the sizeof operator there is used type-name struct rofl.

While in this function

typedef struct rofl { int x; } rofl;

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

with the sizeof operator there is used a primary expression with the variable rofl, that has the type struct rofl *.

Kirkendall answered 15/11, 2017 at 1:23 Comment(0)
E
2

There is no "picking" or "choosing" involved. In both cases the rofl referred to in each sizeof invocation is the variable, not the type, due to scoping rules. The variable is declared at inner scope, and thus overrides the type name. The parenthesization of the argument to the sizeof operator is irrelevant.

Best of luck.

Estep answered 15/11, 2017 at 1:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.