分段错误,大小为 4 的无效写入

Segmentation fault, Invalid write of size 4

我有以下结构:

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;

初始化方式如下:

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;
}

在另一个程序中包含此代码:

  ...

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

  ...

结果是:

Segmentation fault (core dumped)

使用 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

我试图在一个较小的程序中重现它,但没有成功。

将结构更改为 uint64_t 而不是 uint32_t 可以解决问题。

ann_init 内部打印指针 ann->x 我得到 0x55601051f080 和外部 0x1051f08000000000.

为什么会这样?

编辑: 在其中一个包含的文件中找到了罪魁祸首:

#pragma pack(1)

仍然不确定为什么会导致这个问题。

如果我正在做指针运算来访问结构字段,这是有道理的,但我是按名称访问结构字段,那么为什么它会计算出错误的值?

为什么在init函数里面没问题,在外面却访问失败?

根据对问题的评论得出的答案...

假设您在 header:

中定义了一个更简单的结构
// header.h
typedef struct {
  char foo;
  int *ptr;
} fish_t;

和两个源文件:

// 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;
}

第一个文件(没有打包)可能 fish_t 的内存布局如下:

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

但是第二个文件(带包装)看起来像:

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

因此,当第二个文件尝试读取(并随后取消引用)指针时,它实际上正在读取填充中发生的任何内容,这肯定会出错。