3

Due to the way how memory is layout for structs and its members, I was able to do the following:

typedef struct
{
    int a;
    int b;
    int c;
} myStruct;

In main():

myStruct aStruct;
aStruct.a = 1;
aStruct.b = 2;
aStruct.c = 3;

int *aBadBoyPointer = &aStruct.a;

printf("%i %i %i", *(aBadBoyPointer), *(++aBadBoyPointer), *(++aBadBoyPointer));

Easy enough.

The above line causes a warning:

Unsequenced modification and access to aBadBoyPointer

But it compiles and runs fine printing out 1, 2, 3, respectively.

My question here:

For the sake of doing things correctly, could you cite a scenario where this would break miserably, a scenario that confirms the compiler that this is a bad practice / bad way of doing things?

Or: perhaps this is in fact a "a good way" to do things under some rare circumstances?

Addendum:

Aside from this part that cause Undefined Behavior:

printf("%i %i %i", *(aBadBoyPointer), *(++aBadBoyPointer), *(++aBadBoyPointer));

What I really like to know is:

Is it considered OK (a good practice) using a pointer pointing to one of the members of the struct but then gain access to other members within the struct this way (by incrementing, made possible due to the memory layout of the struct members) or is it a practice that is frowned upon by the Standard?

And secondly as mentioned above, if this is in deed a bad practice, would there ever be cases where using a pointer this way within a struct and then gain access to another member variable beneficial under certain circumstances?

Community
  • 1
  • 1
Unheilig
  • 16,196
  • 193
  • 68
  • 98
  • @FoggyDay I don't believe anyone is unholy unless he/she names (nicknames) him/her -self as one. – Unheilig Jan 13 '14 at 20:12
  • 1
    @Unheilig As far as I could understand I believe you want to read properties from a struct that normally would be private/protected? In this case it would be a C++ question, and yes you can do that by playing with pointers. – Havenard Jan 13 '14 at 20:13
  • @Havenard But in `C` members in `struct`s are `public` by default? Anyway, would that be possible using my method here? I am guessing there is more to it gaining access this way? Am all ears. – Unheilig Jan 13 '14 at 20:16
  • In C everything behaves as public, there is no such thing as private/protected in this language. – Havenard Jan 13 '14 at 20:17
  • @Havenard Actually, my question was about C but you got me curious with the pointer badness in C++ :-) As much as the the spaces in comment section allows, how would you do that in C++? Mind casting off a little enlightenment my way on this? :-) – Unheilig Jan 13 '14 at 20:31
  • That's a worthwhile [question][http://stackoverflow.com/questions/8547541/accessing-private-members-c], but the short answer is: you can get to "private" members of C++ classes by bopping around with pointers, it's just not portable. – nmichaels Jan 13 '14 at 20:39

5 Answers5

5

There are a few problems, the warning you are seeing is due the unspecified order of evaluation of the argument to your function. The C99 draft standard section 6.5.2.2 Function calls paragraph 10 says:

The order of evaluation of the function designator, the actual arguments, and subexpressions within the actual arguments is unspecified, but there is a sequence point before the actual call.

you are also modifying a variable more than once within a sequence point which is undefined behavior and can not be relied on to work, section 6.5 Expressions paragraph 2 says (emphasis mine going forward):

Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression.72) Furthermore, the prior value shall be read only to determine the value to be stored.73)

Also, note that the standard allows for padding between elements of a struct but beyond that scalar is considered an array of one element and so incrementing beyond the array and then performing indirection would be undefined as well. This is covered in section 6.5.6 Additive operators paragraph 7:

For the purposes of these operators, a pointer to an object that is not an element of an array behaves the same as a pointer to the first element of an array of length one with the type of the object as its element type.

and going more than one past the array bounds of accessing one past the array bounds is undefined by section 6.5.6 Additive operators paragraph 8:

[...]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. If the result points one past the last element of the array object, it shall not be used as the operand of a unary * operator that is evaluated.

We can see depending on the optimization level gcc will output (see it live):

3 3 2

or (see it live):

3 3 3

neither of which is the desired output.

The standards compliant way to access your structs members via a pointer would be to use offsetof and here, which requires including stddef.h. Accessing member a would look like this:

*( (int*) ((char*)aBadBoyPointer+offsetof(myStruct, a)) )
    ^       ^                    ^
    3       2                    1

There are three elements here:

  1. Use offsetof to determine the offset in bytes of the member
  2. Cast to *char ** since we need pointer arithmetic in bytes
  3. Cast back to *int ** since that is the correct type
Community
  • 1
  • 1
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • IIRC there are sequence points between evaluations of expressions of function arguments. The second part of your answer is really what is wrong with this answer. The pointer arithmetic OP uses to access the `struct`'s elements is also undefined behavior, I believe, although I'm not certain about this. – Andrey Mishchenko Jan 13 '14 at 17:49
  • Oops, it looks like I was wrong and there is no sequence point between functions' arguments' expressions. – Andrey Mishchenko Jan 13 '14 at 17:52
  • 3
    @Andrey there is a sequence point before the function is called but for each argument. It is not the comma operator but just a separator. – Shafik Yaghmour Jan 13 '14 at 17:53
  • 1
    @Unheilig I updated my answer, you are not allowed to access the elements of the struct like that. I would have to think about the standard way to do that is. – Shafik Yaghmour Jan 13 '14 at 21:20
4

I agree with the existing answers (such unsequenced access invokes Undefined Behavior and is bad).

However, to make a concrete example, I compiled your code in MS Visual Studio. The output is (in both Debug and Release mode):

3
3
3
anatolyg
  • 26,506
  • 9
  • 60
  • 134
2

Function arguments are not evaluated in an order determined by the C standard (C99 §6.5.2.2) therefore your code invokes undefined behavior. A different compiler or the same compiler on a different platform might give you different results. Under no circumstances is invoking undefined behavior a good way to do things.

For reference, the text of the standard says:

10 The order of evaluation of the function designator, the actual arguments, and subexpressions within the actual arguments is unspecified, but there is a sequence point before the actual call.

Addendum

To answer the second part of your question, the C compiler is free to add padding in between members of a struct per §6.7.2.1 paragraph 12:

Each non-bit-field member of a structure or union object is aligned in an implementation-defined manner appropriate to its type.

There are cases where structures can behave like arrays, and incrementing a pointer to a member can work out for you (see #pragma pack and __attribute__((packed))) but your code will be decidedly (if explicitly) non-portable and you may run into some compiler bugs. In general, use arrays and enumerations for this sort of thing instead of structures.

nmichaels
  • 49,466
  • 12
  • 107
  • 135
2

In addition to what was already said in Shafik Yaghmour and nmichaels answers, you must also observe that some compilers will apply an alignment to the variables in the structure, usually by 4 bytes. For instance:

struct something {
    char a;
    char b;
};

This structure seems to have 2 bytes, but it may have 8 because the compiler may pad each element in the structure to make it cover a memory space divisible by 4. There will be 6 bytes that are just garbage, but they are still reserved. In such exemple, reading the structure as a sequence of char would fail.

Community
  • 1
  • 1
Havenard
  • 27,022
  • 5
  • 36
  • 62
1

The compiler is free to add padding between each of a struture's members. If it did the OP's code would fail miserably.


Also this might be undefined behaviour as one may not dereference a pointer which points out of an array's bounds.

However, whether one may consider

int a;

an array of 1 element I'm unsure.

alk
  • 69,737
  • 10
  • 105
  • 255
  • That is a good question in your answer. I hope someone can answer that here as well. Interesting. +1. – Unheilig Jan 13 '14 at 20:09