views:

711

answers:

6

What I am asking about is the well known "last member of a struct has variable length" trick. It goes something like this:

struct T {
    int len;
    char s[1];
};

struct T *p = malloc(sizeof(struct T) + 100);
p->len = 100;
strcpy(p->s, "hello world");

Because of the way that the struct is laid out in memory, we are able to overlay the struct over a larger than necessary block and treat the last member as if it were larger than the 1 char specified.

So the question is: Is this technique technically undefined behavior?. I would expect that it is, but was curious what the standard says about this.

PS: I am aware of the C99 approach to this, I would like the answers to stick specifically to the version of the trick as listed above.

+22  A: 

As the C FAQ says:

It's not clear if it's legal or portable, but it is rather popular.

and:

... an official interpretation has deemed that it is not strictly conforming with the C Standard, although it does seem to work under all known implementations. (Compilers which check array bounds carefully might issue warnings.)

The rationale behind the 'strictly conforming' bit is in the spec, section J.2 Undefined behavior, which includes in the list of undefined behavior:

  • An array subscript is out of range, even if an object is apparently accessible with the given subscript (as in the lvalue expression a[1][7] given the declaration int a[4][5]) (6.5.6).

Paragraph 8 of Section 6.5.6 Additive operators has another mention that access beyond defined array bounds is undefined:

If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.

Carl Norum
In the OP's code, `p->s` is never used as an array. It's passed to `strcpy`, in which case it decays to a plain `char *`, which happens to point to an object which can legally be interpreted as `char [100];` inside the allocated object.
R..
Perhaps another way of looking at this is that the language could conceivably restrict how you access actual **array variables** as described in J.2, but there is no way it can make such restrictions for an object allocated by `malloc`, when you have merely converted the returned `void *` to a pointer to [a struct containing] an array. It's still valid to access any part of the allocated object using a pointer to `char` (or preferably `unsigned char`).
R..
@R. - I can see how J2 might not cover this, but isn't it also covered by 6.5.6?
detly
Sure it could! Type and size information could be embedded in every pointer, and any erroneous pointer arithmetic could then be made to trap - see e.g. [CCured](http://hal.cs.berkeley.edu/ccured/). On a more philosophical level, it doesn't matter whether *no possible implementation* could catch you, it's still undefined behavior (there are, iirc, cases of undefined behavior that would require an oracle for the Halting Problem to nail down - which is precisely why they are undefined).
Zack
detly: J2 is just an informative list, 6.5.6 is definitive, R is wrong.
Zack
The object is not an array object so 6.5.6 is irrelevant. The object is the block of memory allocated by `malloc`. Lookup "object" in the standard before you spout off bs.
R..
By the way, the example in J2 depends on the type. If the array happened to be `unsigned char a[4][5];`, then `a[1][7]` is valid because any object can be accessed via a pointer to `unsigned char` as if it were overlaid by an array of type `unsigned char [sizeof object]`.
R..
Whether or not 6.5.6 disallows seems to hinge on whether you consider that the entire block of 100 chars allocated by `malloc()` in the example counts as an array object (of 100 chars) in its own right. (What if the `struct` was stored within a union that included a large char array? Are two pointers that have the same type and must be equal always equivalent?)
caf
@R.. I was not aware that the `unsigned char[]` behaviour you mentioned was in the std... do you recall if that's C89, C99 or both?
detly
I think it's only in C99, but I'm not sure. The purpose seems to be so that you can make functions which operate on abstract memory. My favorite hypothetical one is a `memswap` which swaps two potentially-giant objects without requiring temporary space like using `memcpy` would.
R..
By the way, the reason this only applies to `unsigned char *` and not other pointers is related to strict aliasing rules. Having to assume `int *` and `struct foo *` might point to the same or overlapping memory, like pre-C99 compilers did in practice, was deemed too costly (inhibits too many optimizations). Only allowing `unsigned char *` to alias other objects seems to have been the compromise.
R..
+13  A: 

I believe that technically it's undefined behavior. The standard (arguably) doesn't address it directly, so it falls under the "or by the omission of any explicit definition of behavior." clause (§4/2 of C99, §3.16/2 of C89) that says it's undefined behavior.

The "arguably" above depends on the definition of the array subscripting operator. Specifically, it says: "A postfix expression followed by an expression in square brackets [] is a subscripted designation of an array object." (C89, §6.3.2.1/2).

You can argue that the "of an array object" is being violated here (since you're subscripting outside the defined range of the array object), in which case the behavior is (a tiny bit more) explicitly undefined, instead of just undefined courtesy of nothing quite defining it.

In theory, I can imagine a compiler that does array bounds checking and (for example) would abort the program when/if you attempted to use an out of range subscript. In fact, I don't know of such a thing existing, and given the popularity of this style of code, even if a compiler tried to enforce subscripts under some circumstances, it's hard to imagine that anybody would put up with its doing so in this situation.

Jerry Coffin
+1, this is a good answer
Evan Teran
+3  A: 

It is not undefined behavior, regardless of what anyone, official or otherwise, says, because it is defined by the standard. p->s, except when used as an lvalue, evaluates to a pointer identical to (char *)p + offsetof(struct T, s). In particular, this is a valid char pointer inside the malloc'd object, and there are 100 (or more, dependign on alignment considerations) successive addresses immediately following it which are also valid as char objects inside the allocated object. The fact that the pointer was derived by using -> instead of explicitly adding the offset to the pointer returned by malloc, cast to char *, is irrelevant.

Technically, p->s[0] is the single element of the char array inside the struct, the next few elements (e.g. p->s[1] through p->s[3]) are likely padding bytes inside the struct, which could be corrupted if you perform assignment to the struct as a whole but not if you merely access individual members, and the rest of the elements are additional space in the allocated object which you are free to use however you like, as long as you obey alignment requirements (and char has no alignment requirements).

If you are worried that the possibility of overlapping with padding bytes in the struct might somehow invoke nasal demons, you could avoid this by replacing the 1 in [1] with a value which ensures that there is no padding at the end of the struct. A simple but wasteful way to do this would be to make a struct with identical members except no array at the end, and use s[sizeof struct that_other_struct]; for the array. Then, p->s[i] is clearly defined as an element of the array in the struct for i<sizeof struct that_other_struct and as a char object at an address following the end of the struct for i>=sizeof struct that_other_struct.

Edit: Actually, in the above trick for getting the right size, you might also need to put a union containing every simple type before the array, to ensure that the array itself begins with maximal alignment rather than in the middle of some other element's padding. Again, I don't believe any of this is necessary, but I'm offering it up for the most paranoid of the language-lawyers out there.

Edit 2: The overlap with padding bytes is definitely not an issue, due to another part of the standard. C requires that if two structs agree in an initial subsequence of their elements, the common initial elements can be accessed via a pointer to either type. As a consequence, if a struct identical to struct T but with a larger final array were declared, the element s[0] would have to coincide with the element s[0] in struct T, and the presence of these additional elements could not affect or be affected by accessing common elements of the larger struct using a pointer to struct T.

R..
Can you make arrays of 'hacked structs'? As in `struct hack *p = malloc(42 * (sizeof *p + 100));`?
pmg
You can, but you'll have to do the subscripting yourself, including handling alignment. `p[1]` will not point to the second struct, but rather will overlap with `p->s[4]` or so...
R..
You're right that the nature of the pointer arithmetic is irrelevant, but you're *wrong* about access beyond the declared size of the array. See [N1494](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1494.pdf) (latest public C1x draft) section 6.5.6 paragraph 8 - you're not even allowed to do the *addition* that takes a pointer more than one element past the declared size of the array, and you can't dereference it even if it's just one element past.
Zack
@Zack: that's true if the object is an array. It's not true if the object is an object allocated by `malloc` which is being accessed as an array or if it's a larger struct that's being accessed via a pointer to a smaller struct whose elements are an initial subset of the elements of the larger struct, among other cases.
R..
+1 If `malloc` doesn't allocate a range of memory that can be accessed with pointer arithmetic, what use would it be? And if `p->s[1]` is *defined* by the standard as syntactic sugar for pointer arithmetic, then this answer merely reasserts that `malloc` is useful. What is there left to discuss? :)
Daniel Earwicker
+1  A: 

Yes, it is technically undefined behavior.

Note, that there are at least three ways to implement the "struct hack":

(1) Declaring the trailing array with size 0 (the most "popular" way in legacy code). This is obviously UB, since the zero size array declarations are always illegal in C. Even if it does compile, the language makes no guarantees about the behavior of any constraint-violating code.

(2) Declaring the array with minimal legal size - 1 (your case). In this case any attempts to take pointer to p->s[0] and use it for pointer arithmetic that goes beyond p->s[1] is undefined behavior. For example, a debugging implementation is allowed to produce a special pointer with embedded range information, which will trap every time you attempt to create a pointer beyond p->s[1].

(3) Declaring the array with "very large" size like 10000, for example. The idea is that the declared size is supposed to be larger than anything you might need in actual practice. This method is free of UB with regard to array access range. However, in practice, of course, we will always allocate smaller amount of memory (only as much as really needed). I'm not sure about the legality of this, i.e. I wonder how legal it is to allocate less memory for the object than the declared size of the object (assuming we never access the "non-allocated" members).

AndreyT
In (2), `s[1]` is not undefined behavior. It's the same as `*(s+1)`, which is the same as `*((char *)p + offsetof(struct T, s) + 1)`, which is a valid pointer to a `char` in the allocated object.
R..
On the other hand, I'm almost sure (3) is undefined behavior. Whenever you perform any operation which depends on such a struct residing at that address, the compiler is free to generate machine code which reads from any part of the struct. It could be useless, or it could be a safety feature for strict allocation checking, but there's no reason an implementation couldn't do it.
R..
R: If an array was declared to have a size (is not just the `foo[]` syntactic sugar for `*foo`), then any access beyond the *smaller* of its declared size and its allocated size is UB, regardless of how the pointer arithmetic was done.
Zack
@Zack, you're wrong on several things. `foo[]` in a struct is not syntactic sugar for `*foo`; it's a C99 flexible array member. For the rest, see my answer and comments on other answers.
R..
I was talking about `void x(char foo[])`, and no, I repeat, because the array in the struct hack *has a declared size*, it is the *smaller* of the declared size and the allocated size that counts for purpose of the "past the end of the array" language in 6.5.6p8. So you're still wrong. Neener.
Zack
... I confess that I can't find positive language in the standard to back up that assertion. However, if things were as you claim, then most of section 6.7.2.1p18 (especially the "as if that member were replaced with the largest array that ..." language) would be unnecessary. Therefore I stand on my interpretation.
Zack
The issue is that some members of the committee desperately **want** this "hack" to be UB, because they envision some fairyland where a C implementation could enforce pointer bounds. For better or worse, however, doing so would conflict with other parts of the standard - things like the ability to compare pointers for equality (if bounds were encoded in the pointer itself) or the requirement that any object be accessible via an imaginary overlaid `unsigned char [sizeof object]` array. I stand by my claim that the flexible array member "hack" for pre-C99 has well-defined behavior.
R..
+3  A: 

That particular way of doing it is not explicitly defined in any C standard, but C99 does include the "struct hack" as part of the language. In C99, the last member of a struct may be a "flexible array member", declared as char foo[] (with whatever type you desire in place of char).

Chuck
A: 

If a compiler accepts something like

typedef struct {
  int len;
  char dat[];
};

I think it's pretty clear that it must be ready to accept a subscript on 'dat' beyond its length. On the other hand, if someone codes something like:

typedef struct {
  int whatever;
  char dat[1];
} MY_STRUCT;

and then later accesses somestruct->dat[x]; I would not think the compiler is under any obligation to use address-computation code which will work with large values of x. I think if one wanted to be really safe, the proper paradigm would be more like:

#define LARGEST_DAT_SIZE 0xF000
typedef struct {
  int whatever;
  char dat[LARGEST_DAT_SIZE];
} MY_STRUCT;

and then do a malloc of (sizeof(MYSTRUCT)-LARGEST_DAT_SIZE + desired_array_length) bytes (bearing in mind that if desired_array_length is larger than LARGEST_DAT_SIZE, the results may be undefined).

Incidentally, I think the decision to forbid zero-length arrays was an unfortunate one (some older dialects like Turbo C support it) since a zero-length array could be regarded as a sign that the compiler must generate code that will work with larger indices.

supercat