39

We all know that dereferencing an null pointer or a pointer to unallocated memory invokes undefined behaviour.

But what is the rule when used within an expression passed to sizeof?

For example:

int *ptr = 0;
int size = sizeof(*ptr);

Is this also undefined?

chqrlie
  • 131,814
  • 10
  • 121
  • 189
sashang
  • 11,704
  • 6
  • 44
  • 58
  • 2
    Diffetent languages have different rules. There's no "the rule" until you remove all but one language tag. – n. m. could be an AI Jul 30 '18 at 16:27
  • @n.m. This might be a case of "what's done is done" now. I don't think adding another answer tailored to the different C++ standards would do any harm here. – Baum mit Augen Jul 30 '18 at 16:29
  • 6
    @YSC the accepted answer provides not *a* but *the* definitive norm. – n. m. could be an AI Jul 30 '18 at 16:45
  • 7
    YSC, I'm not sure what else you think could be added to my answer. The ISO standard is *very* explicit in defining the proper behaviour of `sizeof`, there is *no* scope for variation. The fact that your other question that was closed as a dupe of this one was necessary due to a bug in one particular *implementation* of C++ in no way reduces the explicit guarantee of the standard. If you want to quote something in the *standard* that you think nullifies my answers, by all means do so and I'll update it if need be. – paxdiablo Aug 01 '18 at 10:55
  • 1
    @YSC Did you overlook the answer of paxdiablo? It specifically provides standard quote "is not evaluated". – M.M Aug 05 '18 at 01:22
  • @M.M No I didn't, look at the question linked in the bounty text. – YSC Aug 05 '18 at 11:03
  • @YSC I think people are confused with respect to what you require in an answer. From what I can tell you're looking for a definition that says the construct is well-defined. Maybe ask another question specifically about this on SO. – sashang Aug 06 '18 at 00:19
  • I did: https://stackoverflow.com/q/51597864/5470596 – YSC Aug 06 '18 at 07:17

6 Answers6

51

In most cases, you will find that sizeof(*x) does not actually evaluate *x at all. And, since it's the evaluation (de-referencing) of a pointer that invokes undefined behaviour, you'll find it's mostly okay. The C11 standard has this to say in 6.5.3.4. The sizeof operator /2 (my emphasis in all these quotes):

The sizeof operator yields the size (in bytes) of its operand, which may be an expression or the parenthesized name of a type. The size is determined from the type of the operand. The result is an integer. If the type of the operand is a variable length array type, the operand is evaluated; otherwise, the operand is not evaluated and the result is an integer constant.

This is identical wording to the same section in C99. C89 had slightly different wording because, of course, there were no VLAs at that point. From 3.3.3.4. The sizeof operator:

The sizeof operator yields the size (in bytes) of its operand, which may be an expression or the parenthesized name of a type. The size is determined from the type of the operand, which is not itself evaluated. The result is an integer constant.

So, in C, for all non-VLAs, no dereferencing takes place and the statement is well defined. If the type of *x is a VLA, that's considered an execution-phase sizeof, something that needs to be worked out while the code is running - all others can be calculated at compile time. If x itself is the VLA, it's the same as the other cases, no evaluation takes place when using *x as an argument to sizeof().


C++ has (as expected, since it's a different language) slightly different rules, as shown in the various iterations of the standard:

First, C++03 5.3.3. Sizeof /1:

The sizeof operator yields the number of bytes in the object representation of its operand. The operand is either an expression, which is not evaluated, or a parenthesized type-id.

In, C++11 5.3.3. Sizeof /1, you'll find slightly different wording but the same effect:

The sizeof operator yields the number of bytes in the object representation of its operand. The operand is either an expression, which is an unevaluated operand (Clause 5), or a parenthesized type-id.

C++11 5. Expressions /7 (the above mentioned clause 5) defines the term "unevaluated operand" as perhaps one of the most useless, redundant phrases I've read for a while, but I don't know what was going through the mind of the ISO people when they wrote it:

In some contexts ([some references to sections detailing those contexts - pax]), unevaluated operands appear. An unevaluated operand is not evaluated.

C++14/17 have the same wording as C++11 but not necessarily in the same sections, as stuff was added before the relevant parts. They're in 5.3.3. Sizeof /1 and 5. Expressions /8 for C++14 and 8.3.3. Sizeof /1 and 8. Expressions /8 for C++17.

So, in C++, evaluation of *x in sizeof(*x) never takes place, so it's well defined, provided you follow all the other rules like providing a complete type, for example. But, the bottom line is that no dereferencing is done, which means it does not cause a problem.

You can actually see this non-evaluation in the following program:

#include <iostream>
#include <cmath>

int main() {
    int x = 42;
    std::cout << x << '\n';

    std::cout << sizeof(x = 6) << '\n';
    std::cout << sizeof(x++) << '\n';
    std::cout << sizeof(x = 15 * x * x + 7 * x - 12) << '\n';
    std::cout << sizeof(x += sqrt(4.0)) << '\n';

    std::cout << x << '\n';
}

You might think that the final line would output something vastly different to 42 (774, based on my rough calculations) because x has been changed quite a bit. But that is not actually the case since it's only the type of the expression in sizeof that matters here, and the type boils down to whatever type x is.

What you do see (other than the possibility of different pointer sizes on lines other than the first and last) is:

42
4
4
4
4
42
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • 3
    For completeness, in C++ (which doesn't have VLAs) the expression is never evaluated: C++11, 5.3.3 says "The operand is either an expression, which is an unevaluated operand (Clause 5), or a parenthesized type-id." – Mike Seymour Oct 11 '11 at 06:10
  • Cheers, @Mike, incorporated that into the answer for completeness. – paxdiablo Oct 11 '11 at 06:25
  • 2
    @paxdiablo: It may be important to explicitly add that deferencing a null pointer is valid C++ code. It invokes UB when *evaluated*, but it *compiles* just fine. YSC may not be aware of this. – Nicol Bolas Aug 05 '18 at 02:30
  • Why doesn't `*x` result in UB when not evaluated? – xskxzr Aug 05 '18 at 11:45
  • 2
    @xskxzr The same reason that `2+2` does not result in `4` when not evaluated . Honestly it seems like some are just completely ignoring the words "not evaluated" – M.M Aug 05 '18 at 20:44
  • @xskxzr, see the example I added at the end. *None* of the expressions used within the `sizeof` operator are evaluated. Just their *type* matters. – paxdiablo Aug 07 '18 at 00:44
17

No. sizeof is an operator, and works on types, not the actual value (which is not evaluated).

To remind you that it's an operator, I suggest you get in the habit of omitting the brackets where practical.

int* ptr = 0;
size_t size = sizeof *ptr;
size = sizeof (int);   /* brackets still required when naming a type */
C. K. Young
  • 219,335
  • 46
  • 382
  • 435
6

The answer may well be different for C, where sizeof is not necessarily a compile-time construct, but in C++ the expression provided to sizeof is never evaluated. As such, there is never a possibility for undefined behavior to exhibit itself. By similar logic, you can also "call" functions that are never defined [because the function is never actually called, no definition is necessary], a fact that is frequently used in SFINAE rules.

Dennis Zickefoose
  • 10,791
  • 3
  • 29
  • 38
5

sizeof and decltype do not evaluate their operands, computing types only.

Jon Purdy
  • 53,300
  • 8
  • 96
  • 166
2

sizeof(*ptr) is the same as sizeof(int) in this case.

H.S.
  • 11,654
  • 2
  • 15
  • 32
EricS
  • 9,650
  • 2
  • 38
  • 34
  • ptr is a pointer to an integer. *ptr is an integer. sizeof(*ptr) is sizeof( an integer ). – EricS Jan 16 '12 at 16:35
1

Since sizeof does not evaluate its operand (except in the case of variable length arrays if you're using C99 or later), in the expression sizeof (*ptr), ptr is not evaluated, therefore it is not dereferenced. The sizeof operator only needs to determine the type of the expression *ptr to get the appropriate size.

doppelheathen
  • 388
  • 4
  • 8