문제

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?

도움이 되었습니까?

해결책

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

다른 팁

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

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.

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.

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.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top