1

I'm using the code below to find alignment properties of some attributes. I know that storing the NULL pointer is defined behaviour and pointer operations also are defined behaviour and only unreferencing the NULL (and other invalid values) pointer invokes undefined behaviour. My questions is simple: Does the -> implies unreferencing the pointer (thus causing undefined behaviour in the code below)?

#include <iostream>

void f(void *p)
{
  std::cout << p << std::endl;
}

struct X
{
  int a;
  int b;
};

struct Y
{
  int a[2];
  int b;
};

int main()
{
  X *x = NULL;
  Y *y = NULL;
  f(&x->b);
  f(&y->b);
}
vinipsmaker
  • 2,215
  • 2
  • 19
  • 33
  • 1
    It appears to me that both `&x` (and `&y`) and the `->` would cause undefined behavior. One because you are asking for the address of `NULL`, and the other because you are dereferencing a `NULL` pointer. – wolfPack88 Jun 24 '14 at 21:58
  • You have two problems actually. `&x` would be UB as would `x->b`, so that expression is going to cause all kinds of problems. – Cory Kramer Jun 24 '14 at 21:59
  • 1
    @wolfpack88: Actually `&` has lower precedence than `->`, then it is evaluated later: http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B#Operator_precedence – vinipsmaker Jun 24 '14 at 22:00
  • @park-young-bae: no operators overloading here (and I already have this guarantee on my code). – vinipsmaker Jun 24 '14 at 22:02
  • @wolfPack88 Even if the expression was `(&x)->b`, `&x` is not taking the address of NULL (which doesn't make sense anyway), you're taking the address of the variable `x`. In that case the code wouldn't compile because `X*` doesn't have any member named `b`. vinipsmaker: From §5.2.5/2 - *The expression E1->E2 is converted to the equivalent form (*(E1)).E2*. So your code has undefined behavior. – Praetorian Jun 24 '14 at 22:08
  • Have you considered using `alignof`? – zneak Jun 24 '14 at 22:14
  • @zneak: [It didn't work](http://pastebin.com/k4csPY1x) and [I got warnings](http://pastebin.com/uV3JaCPR). From comments below, I think I'll try `offsetof`. – vinipsmaker Jun 24 '14 at 22:39
  • I may have read too fast your question. It does look like you're looking for offsetof, indeed. – zneak Jun 24 '14 at 22:45

2 Answers2

2

The -> operator is a combination of the * and . operators. For

X *x = (struct *)malloc(sizeof(struct));
f(&x->b);

it perform indirection on x to locate the structure it points to, then selects the b member of the structure. It is similar to call

 f( &(*x).b ); // which is same as f( & ( (*x).b ) );

Since in your case x is a NULL pointer, dereferencing a NULL pointer invokes undefined behavior:
C++11: 8.3.2 References (p4):

Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by dereferencing a null pointer, which causes undefined behavior.

haccks
  • 104,019
  • 25
  • 176
  • 264
1

The code in question is not undefined behavior (although it's a bit unconventional). The code is essentially doing a poor-man's offsetof() inside the calls to f(), where you are passing the structure offset of b to that function. The NULL pointer will not be dereferenced, as you are calculating the address of x->b. You are not loading the contents of x->b via the NULL pointer.

A citation for why the code above, equivalent to offsetof, is not undefined behavior, the C++ spec (draft 2012-01-16), on page 427, states:

The macro offsetof(type, member-designator) accepts a restricted set of type arguments in this International Standard. If type is not a standard-layout class (Clause 9), the results are undefined.

The usage in the code above is defined since it is using a standard-layout struct type (struct X and struct Y).

troydj
  • 304
  • 2
  • 5
  • It doesn't need to be a dereference to be UB (even though I believe it is dereferencing, from the language perspective). Only *creating* a pointer further ahead than one element after the end of an array is UB: this means that any manipulation on a null pointer is UB. – zneak Jun 24 '14 at 22:20
  • zneak, could you offer a citation to that UB? – Sophit Jun 24 '14 at 22:20
  • 1
    @zneak, given that the `offsetof()` macro in `stddef.h` uses the same technique in the code above (calculating an address based on a NULL pointer), I would say that this is not undefined behavior (i.e. the Standard C library would not encourage undefined behavior by providing the aforementioned macro). A common definition of `offsetof()` is shown below: `#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)` – troydj Jun 24 '14 at 22:28
  • I should add that Standard C++ also provides the `offsetof()` macro. – troydj Jun 24 '14 at 22:34
  • @troydj, *your* implementation of the offsetof macro uses it: this means that your compiler's team considered this to be valid within their own implementation, for your specific platform. It says nothing about the standard or the portability of it to other compilers or platforms. I agree with you that this technique is unlikely to ever cause a problem, but I also believe that clearly pedantic questions deserve clearly pedantic answers. – zneak Jun 24 '14 at 22:34
  • 1
    @troydj, there has been [quite a bit of discussion](http://stackoverflow.com/questions/6433339/) on the validity of the offsetof macro implemented with a null dereference. GCC now uses a __builtin to handle it in C++ because a type can overload the address-of operator, in which case there is absolutely no doubt that this is UB. – zneak Jun 24 '14 at 22:42
  • Upvoted your answer, because it led to discussion that mentioned the solution that I'm about to use: `offsetof`, but I'm not convinced that this is defined behaviour outside of GCC. – vinipsmaker Jun 24 '14 at 23:02
  • @zneak, I agree with your assessment that not all usage of `offsetof` in C++ is defined behavior. Even in C, using `offsetof` with bitfields, for example, is undefined. However, in the case of the original poster's code snippet and question (where the data types in question are standard layout types), I believe the C++ spec treats it as defined behavior. – troydj Jun 25 '14 at 02:09