3

I was browsing some C++ code recently and I ran into the following line:

static char zsocket_name[sizeof((struct sockaddr_un*)0)->sun_path] = {};

… This is confusing, as it looks to me as if the result of the sizeof operator is being pointer-dereferenced to access a struct field named sun_path, and that value is to be used to size an array in static storage.

However, when I tried a simple snippet program to evaulate the expression sizeof((struct sockaddr_un*)0)->sun_path, it yields the size of the sun_path member of the sockaddr_un struct.

Clearly, that is what the author of the original line was intending; but I find it syntactically confusing as it looks like a pointer dereference on the result of the sizeof(…) operation.

What am I missing about this use of sizeof(…)? Why does this expression evaluate this way?

fish2000
  • 4,289
  • 2
  • 37
  • 76
  • 1
    Check out a [reference](http://en.cppreference.com/w/cpp/language/sizeof). There are two forms. The code should really have a space there, though, because it's rather misleading like this. – chris Feb 04 '17 at 05:19
  • 2
    @chris aha I think I see – as opposed to `sizeof(type)` the `sizeof expression` form has no parenthesis and so any parenthesis that are added are part of the expression. – fish2000 Feb 04 '17 at 05:24
  • 1
    I suggest `std::declval` if you are able to use it, it conveys better meaning and doesn't look like a scary null ptr deref. – user975989 Feb 04 '17 at 05:24
  • @user975989 I am all for conveying meaning better, but how do you mean – `std::declval` is for obtaining the type of an expression in unevaluated context, how would you use it here? – fish2000 Feb 04 '17 at 05:33
  • 1
    I'm surprise that nobody mentioned that sizeof expression doesn't evaluate the expression at runtime, so you can write any grammatically correct expression you want, even one that is undefined behavior if you want... This is why "dereferencing the nul pointer" is correct here, because there is no real dereferencing, just an expression that has a type but is never really evaluated by the machine. – Jean-Baptiste Yunès Feb 04 '17 at 08:43
  • @Jean-BaptisteYunès indeed that is an important note – if it is the case that `sizeof expression` does not evaluate _expression_ that is a key detail. – fish2000 Feb 04 '17 at 08:45
  • 1
    Just try `sizeof ( cout << "hello" << endl );` – Jean-Baptiste Yunès Feb 04 '17 at 08:48
  • 1
    There is two syntax for `sizeof`, `sizeof expression` and `sizeof (type)`. – Jean-Baptiste Yunès Feb 04 '17 at 08:51
  • 1
    @fish2000 You can do `sizeof(std::declval().sun_path)` – user975989 Feb 04 '17 at 10:08
  • For C a duplicate to: http://stackoverflow.com/questions/19785518/is-dereferencing-null-pointer-valid-in-sizeof-operation – alk Feb 04 '17 at 11:46

2 Answers2

4

In C++, the sizeof operator has a form sizeof expression in addition to the more common sizeof(type), so this:

sizeof ((struct sockaddr_un*)0)->sun_path

is equivalent to this:

sizeof(decltype(((struct sockaddr_un*)0)->sun_path))

The former, albeit without whitespace, is what's written in the code you posted.


Note that a parenthesized expression is also an expression, so sizeof ((struct sockaddr_un*)0)->sun_path can also be written with extra parentheses: sizeof(((struct sockaddr_un*)0)->sun_path) — even though this looks like the sizeof(type) form, it's actually the sizeof expression form applied to a parenthesized expression.

The only thing you can't do is sizeof type, so this is invalid:

sizeof decltype(((struct sockaddr_un*)0)->sun_path)

A more modern way of getting at the struct's field in C++, without casting 0 to a pointer, would be to use declval:

sizeof std::declval<sockaddr_un>().sun_path
jtbandes
  • 115,675
  • 35
  • 233
  • 266
  • Actually it would seem (according to the cppreference.com docs) the form with parens is for retrieving the size of a type; without parens, it evaluates to the size of _expression_ – so if you have something like `sizeof(_expression_)` the parens are actually included in _expression_ because it is the non-type form of `sizeof` which has no parens … the reason this is super-confusing is because `sizeof(…)` looks like a function call but it is not, it’s an operator – even though one form of the operator specifically looks more function-call-y. – fish2000 Feb 04 '17 at 05:29
  • Aha, thanks, the `decltype` in your example makes that super-clear in this case. – fish2000 Feb 04 '17 at 05:34
  • 2
    Note that `(expression)` is also an expression. You can get into heated discussions about "do you, or do you not, use parentheses around the expression". Personally, I always use the fully parenthesized form, so I'd write `sizeof(((struct sockaddr_un *)0)->sun_path)` in C — `decltype` is (at best) a non-standard extension in C. It avoids nasty problems like the one in the question. I've had other people vehemently deny the validity of this opinion. We just about stayed on speaking terms after the debate. – Jonathan Leffler Feb 04 '17 at 05:34
4

Your mistake is thinking that sizeof works like a function call, whereas it is actually an operator. There is no requirement to use () at all.

sizeof is actually an operator of the form sizeof expression and the () around expression are not required. The precedence of sizeof in expressions is actually equal to that of ++ and -- (prefix form), unary + and -, ! and ~ (logical and bitwise not), the (type) typecast, & (address of), unary * (pointer indirection), and (C from 2011) _Alignof. All of these have right-to-left associativity.

The only operators with higher precedence than sizeof are ++ and -- (suffix form), function call (()), [] array subscripting, . and -> to access struct members, and (for C only from 1999) compound literals (type){list}.

There is no sizeof(expression) form. sizeof x evaluates the size of the result of an expression x (without evaluating x). sizeof (x) evaluates the size of result of the expression (x), again without evaluating it. You happen to have an expression of the form sizeof a->b which (due to precedence rules) is equivalent to sizeof (a->b) and not to sizeof(a)->b (which would trigger a compilation error).

fish2000
  • 4,289
  • 2
  • 37
  • 76
Peter
  • 35,646
  • 4
  • 32
  • 74
  • Right – the fact that it appeared to be something á la `sizeof(a)->b` was the source of my original confusion; I appreciate the clarification of the operator precedence rules in this case, thanks. – fish2000 Feb 04 '17 at 05:57