程序运行,但 Valgrind 在尝试写入 malloc 的内存时检测到问题

Program runs but Valgrind detecting a problem when attempting to write to malloc'd memory

提问人:Roger Dodger 提问时间:9/12/2023 最后编辑:Vlad from MoscowRoger Dodger 更新时间:9/12/2023 访问量:68

问:

为了学习更多 C,我正在尝试重新创建基本数据结构。这是我尝试使用数组的最小示例,该数组可以编译并运行,但 valgrind 检测到问题:

#include <stdlib.h>
#include <stdio.h>

typedef void * vp_t;

typedef struct {
  int len;
  vp_t *start;
} arr_t;

arr_t * array_new(int len) {
  arr_t *arr = malloc(sizeof(arr_t));
  arr->start = malloc(len * sizeof(vp_t));
  arr->len = len;
  return arr;
}

void array_set(arr_t *arr, int i, vp_t vp) {
  vp_t *dest = arr->start + i * sizeof(vp_t);  
  *dest = vp;
}

int array_get(arr_t *arr, int i) {
  int *p = *(arr->start + i * sizeof(vp_t));
  return *p;
}

void array_delete(arr_t *arr) {
  free(arr->start);
  free(arr);
}

int main() {
  int x=0, y=1, z=2;
  arr_t *arr = array_new(3);
  array_set(arr, 0, &x);
  array_set(arr, 1, &y);
  array_set(arr, 2, &z);

  for (int i = 0; i < 3; ++i) printf("%i ", array_get(arr, i));
  putchar('\n');

  array_delete(arr);
  
  return 0;
}

程序按预期输出。但是,valgrind 在我第二次和第三次调用 array_set 函数时检测到问题。针对此处的示例代码运行 valgrind,我得到:1 2 3

==91933== Invalid write of size 8
==91933==    at 0x109244: array_set (min.c:22)
==91933==    by 0x109312: main (min.c:39)
==91933==  Address 0x4a990d0 is 32 bytes before an unallocated block of size 4,194,032 in arena "client"
==91933== 
==91933== 
==91933== Process terminating with default action of signal 11 (SIGSEGV)
==91933==  Access not within mapped region at address 0x2003A98F4C
==91933==    at 0x109244: array_set (min.c:22)
==91933==    by 0x109327: main (min.c:40)

min.c:22指在array_set函数中。 指 。Valgrind 没有抱怨第 38 行,.*dest = vpmin.c:39array_set(arr, 1, &y)array_set(arr, 0, &x)

我一直在玩 gdb,但我还没有弄清楚。感谢您的观看。

C 分段 - 故障 malloc valgrind 指针 - 算术

评论

2赞 Fe2O3 9/12/2023
和 ,都不要乘以对象的大小。例如:你处理对象。编译器知道每个对象占用了多少字节。set()get()vp_t *dest = arr->start+i;
0赞 Support Ukraine 9/12/2023
尝试打印地址,如下所示:这是您期望的吗?然后打印出来,这是你所期望的吗?printf("%p\n", arr->start + i * sizeof(vp_t));printf("%p\n", arr->start + i );
0赞 Support Ukraine 9/12/2023
阅读 stackoverflow.com/a/67848109/4386427
0赞 Fe2O3 9/12/2023
你把指针和's...你想达到什么目的?编译器应该给你各种警告和错误......vp_tint

答:

0赞 mahdy asady 9/12/2023 #1

当您需要寻址数组时,只需将多少个单元格(除了单元格大小)添加到指针上即可。

void array_set(arr_t *arr, int i, vp_t vp) {
  vp_t *dest = arr->start + i;  
  *dest = vp;
}

int array_get(arr_t *arr, int i) {
  int *p = *(arr->start + i);
  return *p;
}

arr->start 的类型为 vp_t,因此当您向指针添加 1 时,编译器将根据需要增加 (sizeof(vp_t)) 以转到下一个单元格。

评论

3赞 Fe2O3 9/12/2023
set()正在保存结构,而正在返回...好奇。。。get()int
0赞 Mateus Moutinho 9/12/2023 #2

这是解决方案,但首先:为什么你需要一个 void ** 数组?,如果你想创建一个整数数组,使用 int* 创建,一个 void 数组,它只推荐在你需要任何类型的数组时。 无论如何,你正在创建一个 void*(一维),而实际上,它应该是一个二维数组 void **

#include <stdlib.h>
#include <stdio.h>


typedef struct {
  int len;
  void  **start;
} arr_t;

arr_t * array_new(int len) {
  arr_t *arr = malloc(sizeof(arr_t));
  arr->start = malloc(len * sizeof(void*));
  arr->len = len;
  return arr;
}

void array_set(arr_t *arr, int i, void  *vp) {
    arr->start[i] = vp;
}

int array_get(arr_t *arr, int i) {
    return *(int*)arr->start[i];
}

void array_delete(arr_t *arr) {
  free(arr->start);
  free(arr);
}

int main() {
  int x=0, y=1, z=2;
  arr_t *arr = array_new(3);
  array_set(arr, 0, &x);
  array_set(arr, 1, &y);
  array_set(arr, 2, &z);
  for (int i = 0; i < 3; ++i) printf("%i ", array_get(arr, i));
  putchar('\n');

  array_delete(arr);
  
  return 0;
}

但是如果你需要一个任何数组(我认为这是使用 void * 的唯一原因),这里是“正确”的实现


#include <stdlib.h>
#include <string.h>
#include <stdio.h>

enum {
    UNDEFINED,
    STRING,
    INTEGER 
};

typedef struct {
  int len;
  void  **start;
  int *types;
} arr_t;

arr_t * array_new(int len) {
  arr_t *arr = malloc(sizeof(arr_t));
  arr->start = malloc(len * sizeof(void*));
  arr->types = malloc(len *sizeof(int));
  //start everything as undefinde
  memset(arr->types,UNDEFINED,len);
  arr->len = len;
  
  return arr;
}

void array_set_int(arr_t *arr, int i, int value) {
    int *element = malloc(sizeof(int));
    *element = value;
    arr->start[i] =element;
    arr->types[i] = INTEGER;
}

void array_set_str(arr_t *arr, int i, char * value) {
    arr->start[i] = strdup(value);
    arr->types[i] = STRING;
}

int array_get_type(arr_t *arr, int i){
    if(i > arr->len){
        return -1;
    }
    return arr->types[i];
}

int array_get_int(arr_t *arr, int i) {
    return *(int*)arr->start[i];
}

char * array_get_str(arr_t *arr, int i) {
    return (char*)arr->start[i];
}


void array_delete(arr_t *arr) {
  for(int i = 0; i < arr->len;i++){
    if(arr->start[i]){
        free(arr->start[i]);
    }
  }  
  free(arr->types);
  free(arr->start);
  free(arr);
}

int main() {

  arr_t *arr = array_new(3);
  array_set_int(arr, 0, 10);
  array_set_int(arr, 1, 20);
  array_set_str(arr, 2, "aaaa");

  for(int i = 0; i < arr->len; i++){
    int type = array_get_type(arr,i);
    if(type == STRING){
        char *value = array_get_str(arr,i);
        printf("value is %s\n",value);
    }
    if(type == INTEGER){
        int value = array_get_int(arr,i);
        printf("value is %d\n",value);
    }
  }

  array_delete(arr);
  
  return 0;
}

评论

0赞 Fe2O3 9/12/2023
请注意,正在调用 ,传递变量的地址...然而,该函数认为它正在接收...而且,???呢强制转换不适合初学者,尤其是不要强制转换为不相关的数据类型。(另外,这完全是错误的。在这种情况下是无害的,但仍然是错误的。main()set()int*vpget()**start
0赞 Mateus Moutinho 9/12/2023
是的,仍然是错误的,除非他为这些分配所需的内存,否则该值将无法在范围之外生存,但我解决了他的问题,并提出了一个没有任何错误或泄漏内存的代码,让他意识到他的实现产生的未来问题
0赞 Fe2O3 9/12/2023
"而实际上,应该是一个二维数组void**“错误。给初学者的答案质量非常低。看起来只不过是一个有缺陷的代码转储。看起来像是初学者会提交的问题......
0赞 Mateus Moutinho 9/12/2023
他正在使用 void ,这是使用 void 指针的唯一理由,它动态地处理多种数据类型。 而“任何”数组必须是二维虚空指针,我的答案有什么错误?,如果他想要一个 int 数组,他应该使用 int 而不是 void。
0赞 Fe2O3 9/12/2023
太棒了!回答了一个初学者的问题,并将其变成了“通用数据类型存储设施”的开始。但是,前 3 个字节只有零;不是全部 6...并允许调用方越界访问(UB!!)...如果只存储了 2 个值,可能会导致 SEGFAULT...而且,有一种永不失败的信心......仍然不是一个好的答案,imo...memset(arr->types,UNDEFINED,len);array_get_type()array_delete()malloc()
0赞 Vlad from Moscow 9/12/2023 #3

您错误地使用了指针。

例如,让我们考虑函数array_set

void array_set(arr_t *arr, int i, vp_t vp) {
  vp_t *dest = arr->start + i * sizeof(vp_t);  
  *dest = vp;
}

根据 C 标准,像这样的表达式指向数组的元素相对于 ponter 指向的元素pointer + ii-th

因此,如果你需要在函子中获取指向第 i 个元素的指针,你需要编写

  vp_t *dest = arr->start + i;

nstead 的

  vp_t *dest = arr->start + i * sizeof(vp_t);  

所以 functon 看起来像

void array_set(arr_t *arr, int i, vp_t vp) {
  vp_t *dest = arr->start + i;  
  *dest = vp;
}

请注意,下标运算符的计算结果为 。所以函数也可以写成pointer[i]*( pointer + i )

void array_set(arr_t *arr, int i, vp_t vp) {
  arr->start[i] = vp;
}

程序的其他部分也存在同样的问题,您应该相应地更新这些问题。

例如,该函数可能如下所示array_get

int array_get(arr_t *arr, int i) {
  int *p = arr->start[i];
  return *p;
}
0赞 JeremyP 9/12/2023 #4

这是错误的

  vp_t *dest = arr->start + i * sizeof(vp_t);  

在 C 语言中,当您进行指针算术(即向指针添加一个数字)时,编译器将负责将数字乘以指向的对象的大小。例如,如果您有

int64_t a[50];
int *b = a;
int *c = &(a[21]);

b + 8指向 not - 编译器知道指向大小为 8 字节的对象,并将添加的数字乘以 8。同样,将是 21,而不是 168,因为编译器知道将地址的差异除以对象大小。a[8]a[1]bbc - b

在我的示例中,将相同,然后编译器将 64 乘以得到要添加到的字节数。这显然在阵列之外,这就是 Valgrind 在您的案例中检测到的。b + 8 * sizeof(int64_t)b + 64sizeof(int64_t)b

另一种看待这个问题的方法是,在 C 语言中,它们在功能上是相同的。你从来没见过人写信,是吗?您也不需要为指针算术进行乘法运算。a[i]*(a + i)a[i * (sizeof *a)]

之所以需要 in,是因为无法推断块中将包含的对象的类型。sizeofmalloc()malloc()


您的代码还有另一个问题。数组存储指向要存储的对象的指针。这可能是因为您希望能够存储任何类型的对象。但是如果你这样做,你必须小心确保物体不会消失,让你的指针悬空。

例如,x、y 和 z 都是自动示波器。当函数退出时,它们的存储将消失。这不是代码的问题,因为退出函数与退出程序相同。但是,如果您有类似的东西:

int populateArray(arr_t *array)
{
    int x = 1, y = 2, z = 3;
    array_set(arr, 0, &x);
    array_set(arr, 1, &y);
    array_set(arr, 2, &z);
}

int main()
{
  arr_t *arr = array_new(3);
  populateArray(arr);
  // At this point your array contains 3 dangling pointers.
  // Valgrind will complain if you try to access any of them.
}

那坏了。