【问题标题】:Segmentation fault, Invalid write of size 4分段错误,大小为 4 的无效写入
【发布时间】:2020-04-27 03:03:47
【问题描述】:

我有以下结构:

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;

  ...

结果是:

分段错误(核心转储)

使用 valgrind:

==26436== 使用大小为 8 的未初始化值

...

==26436==

==26436== 大小为 4 的无效写入

...

==26436== 地址 0x4c3a78000000000 未堆栈、malloc 或(最近)释放

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

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

ann_init 内部打印指针ann-&gt;x 我得到0x55601051f080 和外部0x1051f08000000000

为什么会这样?

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

#pragma pack(1)

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

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

为什么init函数里面没问题,但是外面访问失败了?

【问题讨论】:

  • 请显示重现问题所需的所有代码。请发布minimal reproducible example。很可能,问题出在其他地方。
  • 在启用调试信息的情况下构建(在构建时添加-g 选项),Valgrind 应该能够为您提供在代码中检测到问题的文件名和行号。还可以使用调试器来捕捉运行中的崩溃,并在代码中定位它发生的位置,并检查该点所有相关变量的值。
  • 我刚刚运行了你的代码,就像你拥有它一样,它运行没有问题。因此,我怀疑您未显示的某些内容可能导致了段错误,或者某处可能潜伏着未定义的行为。我同意 KamilCuk 的建议:编辑您的帖子以包含相关代码区域
  • 这闻起来像是两个程序部分的结构声明不同......要么您更改了结构定义,但没有重新编译一个源文件,而是另一个;或者您使用两个不同的结构定义,即在不同的头文件中。首先删除所有目标文件,然后重新编译
  • “仍然不确定为什么这会导致问题。” 很可能(但在没有看到您的代码的情况下不确定)一个文件在结构定义(或从头文件中包含)之前具有“#pragma”,而一个文件没有。因此,两个文件都认为他们正在查看相同的结构,但一个需要填充,一个不需要。因此,当您取消对指针的引用时,它们位于错误的位置,一切都变得松散了。实际上,您可以在 printf 示例中看到这一点:一个地址的底部与另一个地址的顶部匹配;这不太可能是偶然的。

标签: c memory struct valgrind


【解决方案1】:

从 cmets 得出的问题的答案...

假设您在标题中定义了一个更简单的结构:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-05-09
    • 2016-07-23
    • 2014-01-13
    • 2021-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-25
    相关资源
    最近更新 更多