C语言自定义Vector引发疑似栈损坏、段错误问题排查
问题现象
- 向函数传递参数时,函数接收到的参数值异常,尝试使用该错误值时会触发段错误(Segmentation fault)。
- 程序尚未正式启动执行逻辑前,main函数中的
argc、argv参数就已被损坏,GDB调试输出见下图:
相关代码
main.c
#include <stdio.h> #include <stdlib.h> #include "util/BasicTypes.h" #include "util/IncludeOpengl.h" #include "util/Vec2.h" #include "util/Vec3.h" #include "util/Vec4.h" #include "util/Vector.h" #if !(defined(__linux__) || defined(_WIN64)) STATIC_ASSERT(false, unsupported_system) #endif i32 main(i32 argc, ch8** argv) { //u32 a[] = {1,2,3,4}; //u32* b = a; u32 i; Vector v; init_Vector(&v, sizeof(u32)); for (i = 0; i < 4; i++) { printf("%i\n", i); Vector_append(&v, &i); } for (i = 0; i < Vector_getSize(&v); i++) { printf("%i\n", Vector_get(&v, i)); } destruct_Vector(&v); return 0; }
vector.c
#ifndef VECTOR_C #define VECTOR_C #include "../util/Vector.h" #include "../util/BasicTypes.h" #include "../util/Assert.h" void init_Vector(Vector* self, u32 elementSize) { self->elementSize = elementSize; self->elements = NULL; self->elementCount = 0; } void destruct_Vector(Vector* self) { if (self->elements) { heapFree(self->elements); } } void Vector_append(Vector* self, void* element) { if (self->elements) { self->elements = heapRealloc(self->elements, self->elementCount + 1); memcpy(((byte*)self->elements) + (self->elementCount) * self->elementSize, element, self->elementSize); self->elementCount += 1; return; } else { self->elements = heapAlloc((usize)self->elementSize); memcpy(self->elements, element, self->elementSize); self->elementCount += 1; return; } } void Vector_remove(Vector* self, u32 id) { if (id < self->elementCount) { void* temp = self->elements; self->elements = heapAlloc((self->elementCount - 1) * self->elementSize); memcpy(self->elements, temp, id * self->elementSize); memcpy(((byte*)self->elements) + id * self->elementSize, ((byte*)temp) + (id + 1) * self->elementSize, (self->elementCount - id - 1) * self->elementSize); self->elementCount -= 1; } else { ASSERT(false, "(Vector_remove) id is not in the list"); } } void Vector_insert(Vector* self, void* element, u32 id) { if (!self->elements && !id) { self->elements = heapAlloc(self->elementSize); self->elementCount = 1; } else { if (id < self->elementCount + 1) { void* temp = self->elements; self->elements = heapAlloc((self->elementCount - 1) * self->elementSize); memcpy(self->elements, temp, id * self->elementSize); memcpy(((byte*)self->elements) + (id + 1) * self->elementSize, ((byte*)temp) + (id) * self->elementSize, (self->elementCount - id - 1) * self->elementSize); memcpy(((byte*)self->elements), ((byte*)temp), self->elementSize); self->elementCount += 1; } else { ASSERT(false, "(Vector_insert) id is too far"); } } } void* Vector_get(Vector* self, u32 id) { return ((byte*)self->elements) + id * self->elementSize; } u32 Vector_getSize(Vector* self) { return self->elementCount; } #endif // VECTOR_C
vector.h
#ifndef VECTOR_H #define VECTOR_H #include "../util/BasicTypes.h" typedef struct Vector { void* elements; u32 elementSize; u32 elementCount; } Vector; void init_Vector(Vector* self, u32 elementSize); void destruct_Vector(Vector* self); void Vector_append(Vector* self, void* element); void Vector_remove(Vector* self, u32 id); void Vector_insert(Vector* self, void* element, u32 id); void* Vector_get(Vector* self, u32 id); u32 Vector_getSize(Vector* self); #endif // VECTOR_H
BasicTypes.h
#ifndef BASIC_TYPES_H #define BASIC_TYPES_H #include <stdio.h> #include <stdint.h> #include <stdbool.h> typedef int8_t i8; typedef int16_t i16; typedef int32_t i32; typedef int64_t i64; typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; typedef i8 ch8; typedef i16 ch16; typedef i32 ch32; typedef i64 ch64; typedef u8 uch8; typedef u16 uch16; typedef u32 uch32; typedef u64 uch64; typedef float f32; typedef double f64; typedef long double f128; typedef u8 byte; typedef size_t usize; #endif // BASIC_TYPES_H
根因分析
核心问题是动态内存分配时的大小计算错误,触发堆缓冲区溢出,直接破坏了栈上main函数的参数数据:
- 直接触发崩溃的bug在
Vector_append函数:当vector已有元素执行扩容时,heapRealloc传入的新内存大小写的是self->elementCount + 1,没有乘以单个元素的字节数self->elementSize。
以存储u32(单元素4字节)为例:第一次append走空指针分支,分配4字节存1个元素正常;第二次append时,实际需要2*4=8字节存储2个u32,但代码只申请了1+1=2字节,后续memcpy往这块不足的内存写入4字节数据时,就会越界覆盖堆相邻内存,甚至破坏栈帧里保存的argc、argv值,最终触发段错误。 - 其他隐藏bug:
Vector_insert函数插入元素需要的总大小应为(self->elementCount + 1) * self->elementSize,代码里写的是(self->elementCount - 1) * self->elementSize,分配的内存严重不足,同样会触发溢出。Vector_remove、Vector_insert函数中重新分配内存后,没有释放temp保存的旧内存指针,会造成内存泄漏。
修复方案
- 修正
Vector_append中的扩容大小计算,将realloc那行改为:self->elements = heapRealloc(self->elements, (self->elementCount + 1) * self->elementSize); - 修正
Vector_insert中的内存分配大小,将分配逻辑改为申请(self->elementCount + 1) * self->elementSize字节的内存。 - 在
Vector_remove、Vector_insert完成内存拷贝后,调用heapFree(temp)释放旧内存,避免泄漏。 - 建议给vector增加容量字段,不要每次append都执行realloc,可按2倍速扩容减少内存分配开销,同时做边界检查避免越界访问。
内容的提问来源于stack exchange,提问作者brni-dev
相关产品推荐
相关产品推荐

