1
#include <stdio.h>
  typedef struct ss {
    int a;
    char b;
    int c;
  } ssa;

int main(){
  ssa *ss;
  int *c=&ss->a;
  char *d=&ss->b;
  int *e=&ss->c;

  *c=1;
  *d=2;
  *e=3;

  printf("%d=%p %d=%p %d=%p\n",*c,c++,*c,c++,*c,c);
  return 0;
}


//prints 1=0x4aaa4333ac68 2=0x4aaa4333ac6c 3=0x4aaa4333ac70



 My thinking of how should be the memory structure:
 int        |  char  | int
(68 69 6A 6B)  (6C)  (6D 6E 6F 70)

I'm trying to understand how this code works in memory.

Why int *e starts from 0x...70?

Why c++ (increment) from char (6C) goes 4 bytes more?

Thanks.

Mike L
  • 1,955
  • 2
  • 16
  • 18
  • Your `struct` has size of 12 on my platform. The compiler adds 3 bytes after the first `int` to maintain byte alignment. – Engineer2021 Apr 11 '14 at 12:57
  • "Why c++ (increment) from char (6C) goes 4 bytes more?" - `c` points to `int`. Incrementing a pointer makes it point to the next item after the one it's currently pointing to. The next `int` starts 4 bytes later than the current `int`. – M.M Apr 11 '14 at 13:39

2 Answers2

3

First of all, these lines are illegal:

  *c=1;
  *d=2;
  *e=3;

All you have is a pointer to ssa, but you haven't actually allocated any space for the pointed-to object. Thus, these 3 lines are trying to write into unallocated memory, and you have undefined behavior.

Structure layout in memory is such that member fields are in increasing memory addresses, but the compiler is free to place any amount of padding in between for alignment reasons, although 2 structures sharing the same initial elements will have the corresponding members at the same offset. This is one reason that could justify the "gaps" between member addresses.

You should be more careful with how you call printf(). Argument evaluation order is undefined. You are changing the value of c more than once in between 2 sequence points (see Undefined behavior and sequence points). Furthermore, pointer arithmetic is only guaranteed to work correctly when performed with pointers that point to elements of the same array or one past the end.

So, in short: the code has undefined behavior all over the place. Anything can happen. A better approach would have been:

#include <stdio.h>
  typedef struct ss {
    int a;
    char b;
    int c;
  } ssa;

int main() {
  ssa ss = { 0, 0, 0 };
  int *c = &ss.a;
  char *d = &ss.b;
  int *e = &ss.c;

  printf("c=%p d=%p e=%p\n", (void *) c, (void *) d, (void *) e);
  return 0;
}

The cast to void * is necessary. You will probably see a gap of 3 bytes between the value of d and e, but keep in mind that this is highly platform dependant.

Community
  • 1
  • 1
Filipe Gonçalves
  • 20,783
  • 6
  • 53
  • 70
  • c++ increments using struct also has undefined behavior? – Mike L Apr 11 '14 at 13:05
  • @MikeL I'm sorry, I don't think I understand your question. Can you be more explicit? – Filipe Gonçalves Apr 11 '14 at 13:07
  • If I increment the pointer like this printf("%d=%p %d=%p %d=%p\n",*c,c++,*c,c++,*c,c); on struct is it ok, or it has undefined behavior? assuming everything else in the code is correct. – Mike L Apr 11 '14 at 13:09
  • @MikeL You can't change the value of a variable more than once between 2 sequence points. So no, it's not ok. Failing to follow the rule gives you *undefined behavior*, which means that your program is completely broken and the results are unpredictable. NOTE: appearing to work as expected is a valid form of undefined behavior. – Filipe Gonçalves Apr 11 '14 at 13:10
  • Your "better" code still has undefined behaviour for dereferencing an uninitialized pointer. I'd suggest `ssa ss; int *c = &ss.a;` etc. – M.M Apr 11 '14 at 13:37
  • @MattMcNabb You're wrong. The code does not dereference the pointer. The pointer does not need to be dereferenced to compute the structure members' addresses. Member offsets are known at compile time, the value of `c` is just `((char *) ss) + offsetof(struct ss, a)`. – Filipe Gonçalves Apr 11 '14 at 14:39
  • Evaluating `ss` is UB because it is uninitialized. The `->` operator always dereferences its operand , but that's moot here as `ss` is evaluated first anyway, causing UB. (The only "exception" to all this is the operand of `sizeof`, which is explicitly defined as not evaluating its operand). – M.M Apr 11 '14 at 23:38
  • @MattMcNabb I agree with you that the evaluation of `ss` is undefined. Didn't catch that one - thanks for noting. I edited my answer. About the `->` issue: I believe that in GNU C, `&a->b` doesn't dereference the pointer (the linux kernel defines `offsetof()` in a way that would crash if the pointer was dereferenced), but it is certainly not portable and it would be bad to keep it in my answer, so I also fixed that. You are right that standard C doesn't allow such thing. Thanks for the feedback! – Filipe Gonçalves Apr 12 '14 at 11:32
1

There is often padding inside structures, you cannot assume that each field follows the one before it immediately.

The padding is added by the compiler to make structure member access quick, and sometimes in order to make it possible. Not all processors support unaligned accesses, and even those that do can have performance penalties for such accesses.

You can use offsetof() to figure out where there is padding, but typically you shouldn't care.

unwind
  • 391,730
  • 64
  • 469
  • 606