Search code examples
cmemorystructvalgrind

Segmentation fault, Invalid write of size 4


I have the following struct:

typedef struct ann_t {
  uint32_t xs;
  uint32_t hs;
  uint32_t ys;
  float *x;
  float *h;
  float *y;
  float **wxh;
  float **why;
} ann_t;

Initialized in the following way:

ann_t* ann_init(uint32_t xs, uint32_t hs, uint32_t ys) {
  ann_t *ann = malloc(sizeof(ann_t));
  ann->xs = xs;
  ann->hs = hs;
  ann->ys = ys;
  ann->x = calloc(xs, sizeof(float));
  ann->h = calloc(hs, sizeof(float));
  ann->y = calloc(ys, sizeof(float));

  
  ann->wxh = calloc(xs, sizeof(float*));
  ann->why = calloc(hs+1, sizeof(float*));

  
  int i, j;
  for(i = 0; i < xs; i++) {
    ann->wxh[i] = malloc(hs * sizeof(float));
  }

  for(i = 0; i < hs+1; i++) {
    ann->why[i] = malloc(ys * sizeof(float));
  }
  // printf("%p\n", ann->x);
  return ann;
}

Including this code in another program:

  ...

  ann_t *ann = ann_init(25, 10, 4);
  // printf("%p\n", ann->x);
  ann->x[0] = 1.0;

  ...

The result is:

Segmentation fault (core dumped)

Using valgrind:

==26436== Use of uninitialised value of size 8

...

==26436==

==26436== Invalid write of size 4

...

==26436== Address 0x4c3a78000000000 is not stack'd, malloc'd or (recently) free'd

I tried to reproduce this in a smaller program but couldn't.

Changing the struct to have uint64_t instead of uint32_t solves the problem.

Printing the pointer ann->x inside ann_init I get 0x55601051f080 and outside 0x1051f08000000000.

Why does this happen?

EDIT: Found the culprit in one of the included files:

#pragma pack(1)

Still not sure why this causes the problem.

If I was doing pointer arithmetic to access the struct fields this would make sense but I'm accessing the struct field by name so why does it calculate the wrong value?

Why is it fine inside the init function but outside it the access fails?


Solution

  • An answer derived from the comments to the question...

    Assume you have a simpler structure defined in a header:

    // header.h
    typedef struct {
      char foo;
      int *ptr;
    } fish_t;
    

    and two source files:

    // src1.c
    #include "header.h"
    
    int dummy_int = 5;
    
    fish_t my_fish;
    my_fish.foo = 'a';
    my_fish.ptr = &dummy_int;
    
    use_fish_fn( &my_fish );
    
    // src2.c
    #pragma pack(1)
    #include "header.h"
    
    void use_fish_fn( fish_t *f )
    {
      int bar = *f->ptr;
    }
    

    The first file (without packing) likely sees fish_t as having a memory layout like:

    0: |  foo  |  pad  |  pad  |  pad  |     // one byte char, three bytes padding
    4: |              ptr              |     // four byte pointer
    

    but the second file (with packing) see it like:

    0: |  foo  |        ptr ...        |     // one byte char, 3/4 of pointer
    4: |...ptr |                             // last part of pointer
    

    So when the second file tries to read (and subsequently dereference) the pointer it's actually reading whatever happened to be in the padding, and that's definitely going to go wrong.