25

The C code likes this:

 #include <stdio.h>
 #include <unistd.h> 
 #define DIM(a) (sizeof(a)/sizeof(a[0])) 
 struct obj
 {
     int a[1];
 };
 int main()
 {
     struct obj *p = NULL;
     printf("%d\n",DIM(p->a));
     return 0;
 }

This object pointer p is NULL, so, i think this p->a is illegal. But i have tested this code in Ubuntu14.04, it can execute correctly. So, I want to know why...


Note: the original code had int a[0] above but I've changed that to int a[1] since everyone seems to be hung up on that rather than the actual question, which is:

Is the expression sizeof(p->a) valid when p is equal to NULL?

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
Yang. fr
  • 305
  • 3
  • 8
  • 9
    Not what's happening here, but don't assume that "illegal" things (undefined behaviour) don't appear to work. They can, in fact, appear to work for a very long time before something as simple as a compiler version upgrade breaks them. – chris Aug 19 '15 at 02:34
  • The title said C, the first sentence said C, the headers are C headers, you've used `printf` rather than `cout`, there's not even a little bit of C++ code in the question, so I'm at a total loss as to why it was tagged C *and* C++ :-) Still, it's a good question. – paxdiablo Aug 19 '15 at 02:44
  • 2
    Chris Beck is absolutely correct: your `#define DIM()` macro is *compile time*. It's only the *type* that matters here: not the actual run-time values. – paulsm4 Aug 19 '15 at 02:45
  • Based on the code here `p->a` does not appear to be an illegal operation, whether or not the `printf(...)` statement will produce something meaningful is a different question. Based on the code there is nothing that is strictly speaking "illegal" going on, can you give a more specific example of where this behavior might create a problem? – Matt Aug 19 '15 at 02:55
  • @paulsm4, the macro itself is a compile time operation but I'm pretty certain there's nothing in the standard saying the sizeof *has* to be so. In fact, for VLAs, it cannot be. For efficiency, non-VLAs are probably best done at compile time but language lawyers need to draw the distinction between "must be so" and "probably is so" (or even "almost certainly is so"). – paxdiablo Aug 19 '15 at 03:00
  • 2
    @paxdiablo it's 2015, isn't it about time to stop treating C++ as a superset of C? – M.M Aug 19 '15 at 03:09
  • @Matt, I thought that's what I was doing, by removing the C++ tag. Maybe I should have been clearer. – paxdiablo Aug 19 '15 at 03:14
  • 2
    @paxdiablo all of this code is valid C++ except for `int a[0];` which is invalid in both languages. OP could be using a C++ compiler, we don't know – M.M Aug 19 '15 at 03:16
  • @paxdiablo: i didn't know about the behavior of sizeof with VLAs -- I think my answer is correct in C++ anyways. (Not that it's relevant to the question I'm just trying to keep it straight for my benefit.) I'm looking in section 5.3.3 "Sizeof", in clause 1 it says that when applied to an expression, the operand is unevaluated. (full stop) – Chris Beck Aug 19 '15 at 03:17
  • @ChrisBeck yes, C++ doesn't have VLAs. paxdiablo is right that there is no formal distinction between "compile time" and "run time" – M.M Aug 19 '15 at 03:23
  • 1
    `int a[0];` is *not* illegal in C (as in the C standard), it's invalid in a strictly conforming program, just *one* of the program types described in the standard. – paxdiablo Aug 19 '15 at 03:49
  • @paxdiablo my understanding is that "illegal" or "invalid" is common terminology for a program with constraint violations or undefined behaviour . The C11 standard has a term "correct program" but does not define that very clearly. – M.M Aug 19 '15 at 05:17
  • @Matt, then it looks like it's just a difference in terminology. I read "illegal" as something you're not actually *allowed* to do (would have to be rejected by a compiler). On the correct/conforming/strictly-conforming stuff, yes, I remember that being very difficult to sort out. We did it eventually but it gave lots of headaches along the way :-) – paxdiablo Aug 19 '15 at 05:21
  • @paxdiablo is there another SO thread on the topic? I don't see anywhere in C11 where it says that programs with constraint violations may or may not be rejected by a conforming implementation. The only cases are that "correct program" (whatever that means) must be translated, and `#error` must be rejected. – M.M Aug 19 '15 at 05:26
  • @Matt, I think that's covered by the last bit of section 4, point 6: `A conforming implementation may have extensions (including additional library functions), provided they do not alter the behavior of any strictly conforming program.`. The idea, I believe, is to ensure *every* conforming implementation will correctly run a strictly conforming program, but *may* also run non-conforming programs as well. Perhaps this is worthy of another question rather than continuing here? – paxdiablo Aug 19 '15 at 05:38
  • Is the fact that zero-size array is illegal in C really so poorly known? – this Aug 19 '15 at 06:17
  • 3
    @this: OMG, that's hilarious :-) This question actually has nothing to do with zero length arrays, though I'll admit it may seem that way given the sidetracking happening in the comments. The question is simply to do with whether `sizeof(p->a)` is valid when `p == NULL`. In fact, given the confusion being caused, I think I'll go edit that zero out. – paxdiablo Aug 19 '15 at 06:25

4 Answers4

35

Because sizeof is a compile time construction, it does not depend on evaluating the input. sizeof(p->a) gets evaluated based on the declared type of the member p::a solely, and becomes a constant in the executable. So the fact that p points to null makes no difference.

The runtime value of p plays absolutely no role in the expression sizeof(p->a).

In C and C++, sizeof is an operator and not a function. It can be applied to either a type-id or an expression. Except in the case that of an expression and the expression is a variable-length array (new in C99) (as pointed out by paxdiablo), the expression is an unevaluated operand and the result is the same as if you had taken sizeof against the type of that expression instead. (C.f. C11 references due to paxdiablo below, C++14 working draft 5.3.3.1)

Chris Beck
  • 15,614
  • 4
  • 51
  • 87
16

First up, if you want truly portable code, you shouldn't be attempting to create an array of size zero1, as you did in your original question, now fixed. But, since it's not really relevant to your question of whether sizeof(p->a) is valid when p == NULL, we can ignore it for now.

From C11 section 6.5.3.4 The sizeof and _Alignof operators (my bold):

2/ 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.

Therefore no evaluation of the operand is done unless it's a variable length array (which your example is not). Only the type itself is used to figure out the size.


1 For the language lawyers out there, C11 states in 6.7.6.2 Array declarators (my bold):

1/ In addition to optional type qualifiers and the keyword static, the [ and ] may delimit an expression or *. If they delimit an expression (which specifies the size of an array), the expression shall have an integer type. If the expression is a constant expression, it shall have a value greater than zero.

However, since that's in the constraints section (where shall and shall not do not involve undefined behaviour), it simply means the program itself is not strictly conforming. It's still covered by the standard itself.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • But arrays of length zero are illegal in Standard C, though GCC allows them as an extension. Flexible array members don't count — the dimension there is not 0 so much as unspecified. – Jonathan Leffler Aug 19 '15 at 03:28
  • @Jonathan, I'm not sure that's relevant to the question itself, as you could fix it by simply turning that `0` into a `1` and the question would still be valid, since it's about whether or not `sizeof(p->a)` is illegal when `p` is `NULL`. Still, I'll include it in the answer for completeness. – paxdiablo Aug 19 '15 at 03:31
  • The structure declaration is illegal — whether it is used for anything or not. A program containing that struct type is not standard-conforming C. – Jonathan Leffler Aug 19 '15 at 03:32
  • 2
    @Jonathan, I have to disagree there, the standard allows for non-conformance. It specifically mentions conforming, strictly conforming, and non-conforming programs and implementations, and allows implementation to be conforming provided their extensions don't change the behaviour of strictly conforming programs. So, while the statement that the code is not a strictly conforming one is correct, that *doesn't* mean it's illegal C as per the standard. – paxdiablo Aug 19 '15 at 03:47
  • Sorry to be a pain/pedant to all and sundry, it's just we had to cover all this ground when I was working on standards-compliance for a previous employer's compilers. I'm *still* suffering from those encounters :-) – paxdiablo Aug 19 '15 at 03:59
  • In C program, how can we define a variable length array? For example, `int *p = (int*)malloc(10 * sizeof(int))` is a variable length array? If it is a variable length array, the value of `sizeof(p)` will be decided at runtime? – Yang. fr Aug 19 '15 at 04:51
  • 1
    @Yang.fr `int x = rand() % 10 + 1; int p[x];` – M.M Aug 19 '15 at 05:07
  • ISO 9899:2011 §5.1.1.3 **Diagnostics** _A conforming implementation shall produce at least one diagnostic message (identified in an implementation-defined manner) if a preprocessing translation unit or translation unit contains a violation of any syntax rule or constraint, even if the behavior is also explicitly specified as undefined or implementation-defined._ And in §6.7.6.2 **Array declarators** ¶1 (under 'Constraints'): _If the expression is a constant expression, it shall have a value greater than zero._ The code shown in the question requires a diagnostic from a conforming compiler. – Jonathan Leffler Aug 19 '15 at 06:15
  • Clearly, if the compiler is not conforming to the C standard, then the C standard doesn't impose any requirement on it, but it is hard to say much about the compiler or the language it accepts without a specification of the alternative language. However, there's not much point in further discussion; this answer recognizes that the 0-size array is on tenuous ground, and we can leave it at that. – Jonathan Leffler Aug 19 '15 at 06:15
  • @Jonathan, I have no problem with that, I think you're spot on. I'd just like to add that a diagnostic doesn't mean the code is illegal - a warning is a diagnostic, after all (I think it is, anyway). But, given the amount of time we've all spent on what's really a peripheral issue to the actual question, maybe we should ask another question on the language lawyer aspects, rather than further cluttering up the comments here and risk having hidden what is, after all, a deep discussion. – paxdiablo Aug 19 '15 at 06:23
6

This code contains a constraint violation in ISO C because of:

struct obj
{
    int a[0];
};

Zero-sized arrays are not permitted anywhere. Therefore the C standard does not define the behaviour of this program (although there seems to be some debate about that).

The code can only "run correctly" if your compiler implements a non-standard extension to allow zero-sized arrays.

Extensions must be documented (C11 4/8), so hopefully your compiler's documentation defines its behaviour for struct obj (a zero-sized struct?) and the value of sizeof p->a, and whether or not sizeof evaluates its operand when the operand denotes a zero-sized array.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • 3
    Note that GCC is a compiler that implements a non-standard extension to allow zero-sized arrays. – Jonathan Leffler Aug 19 '15 at 03:31
  • 1
    Perhaps (after reading comments to other questions), you may want to tone down the "illegal" bit. It's no so much illegal as it is invalid in a strictly conforming program. Implementations are specifically allowed to provide extensions provided they don't change the behaviour of strictly conforming programs. Hence they're covered by ISO C as well. And the bit restricting zero-sized array in in a constraints section which has nothing to do with UB. – paxdiablo Aug 19 '15 at 03:51
  • @paxdiablo have changed wording. In C++ constraint violations are explicitly UB; but it looks like the C standard writers have chosen to be vague instead ... – M.M Aug 19 '15 at 05:04
  • See my comments under @paxdiablo's answer. The code in the question requires a diagnostic from a conforming compiler. If the compiler isn't conforming to the standard, then the standard has no affect on the compiler and it can do what the hell it pleases. – Jonathan Leffler Aug 19 '15 at 06:19
  • @JonathanLeffler yes, but what can/must the compiler do after giving the diagnostic? – M.M Aug 19 '15 at 06:59
1

sizeof() doesn't care a thing about the content of anything, it merely looks at the resulting type of the expression.

Since C99 and variable length arrays, it is computed at run time when a variable length array is part of the expression in the sizeof operand.Otherwise, the operand is not evaluated and the result is an integer constant

Zero-size array declarations within structs was never permitted by any C standard, but some older compilers allowed it before it became standard for compilers to allow incomplete array declarations with empty brackets(flexible array members).

ameyCU
  • 16,489
  • 2
  • 26
  • 41