我用C做了一个简单的字符串向量。但是测试代码崩溃了,我不知道哪个部分是错误的。
这些是我的代码。
// vec.h
#include <stdlib.h>
#define VEC_CAPACITY_MULTIPLE 4
typedef struct my_vec {
size_t length;
size_t capacity;
char **strings;
} my_vec;
my_vec* my_vec_new();
void my_vec_push(my_vec *vec, const char *str);
const char* my_vec_get(my_vec *vec, size_t index);
void my_vec_free(my_vec *vec);
和实施。
// vec.c
#include "vec.h"
#include <string.h>
my_vec* my_vec_new()
{
my_vec *vec = malloc(sizeof(my_vec));
vec->length = 0;
vec->capacity = VEC_CAPACITY_MULTIPLE;
vec->strings = malloc(sizeof(char*) * vec->capacity);
return vec;
}
void my_vec_push(my_vec *vec, const char *str)
{
vec->strings[vec->length] = malloc(sizeof(char) * strlen(str) + 1);
strcpy(vec->strings[vec->length], str);
vec->length++;
if (vec->length == vec->capacity) {
char **new_strings = malloc(
sizeof(char*)
*
vec->capacity + VEC_CAPACITY_MULTIPLE
);
for (size_t i = 0; i < vec->length; ++i) {
new_strings[i] = malloc(sizeof(char) * strlen(str) + 1);
strcpy(new_strings[i], vec->strings[i]);
free(vec->strings[i]);
}
free(vec->strings);
vec->strings = new_strings;
vec->capacity += VEC_CAPACITY_MULTIPLE;
}
}
const char* my_vec_get(my_vec *vec, size_t index)
{
return vec->strings[index];
}
void my_vec_free(my_vec *vec)
{
for (size_t i = 0; i < vec->length; ++i) {
free(vec->strings[i]);
}
free(vec);
}
和测试代码。
// test_vec.c
#include <stdio.h>
#include "vec.h"
int main()
{
my_vec *vec = my_vec_new();
my_vec_push(vec, "Hello");
my_vec_push(vec, ",");
my_vec_push(vec, "world");
my_vec_push(vec, "!");
my_vec_push(vec, "foo");
my_vec_push(vec, "bar");
my_vec_push(vec, "baz");
printf("vec capacity: %ldn", vec->capacity);
printf("vec length: %ldn", vec->length);
for (size_t i = 0; i < vec->length; ++i) {
printf("%sn", my_vec_get(vec, i));
}
return 0;
}
但出来是这样的,
vec capacity: 8
vec length: 7
���ojU
,
world
!
foo
bar
baz
第一个字符串"Hello"发生了什么?我已经将 printf 放在my_vec_push
的重新分配部分中,没有问题。仅在功能my_vec_get
发生。但是这个函数只是简单地返回指向给定索引的指针。
在第 31 行,分配strlen(str) + 1
个字节。(sizeof(char)
保证1
。
new_strings[i] = malloc(sizeof(char) * strlen(str) + 1);
在第 32 行,复制strlen(vec->strings[i]) + 1
个字节。
strcpy(new_strings[i], vec->strings[i]);
失 配!
您可以使用以下内容:
new_strings[i] = malloc(strlen(vec->strings[i]) + 1);
strcpy(new_strings[i], vec->strings[i]);
free(vec->strings[i]);
您还可以使用以下方法:
new_strings[i] = strdup(vec->strings[i]);
free(vec->strings[i]);
但是为什么要复制字符串呢?您可以简单地复制指针!
new_strings[i] = vec->strings[i];
这给您留下了以下内容:
for (size_t i = 0; i < vec->length; ++i) {
new_strings[i] = vec->strings[i];
}
该循环可以很容易地编写如下:
memmove(new_strings, vec->strings, vec->length * sizeof(*new_strings));
<小时 />但你还没有走出困境!以下内容也是不正确的:
char **new_strings = malloc(
sizeof(char*)
*
vec->capacity + VEC_CAPACITY_MULTIPLE
);
这是因为
sizeof(char*) * vec->capacity + VEC_CAPACITY_MULTIPLE
方法
( sizeof(char*) * vec->capacity ) + VEC_CAPACITY_MULTIPLE
但你想要
sizeof(char*) * ( vec->capacity + VEC_CAPACITY_MULTIPLE )
但是,为什么不使用realloc
而不是malloc
+memmove
?
// Returns 0 and sets errno on error.
int my_vec_push(my_vec *vec, const char *str)
{
if (vec->length == vec->capacity) {
size_t new_capacity = vec->capacity + VEC_CAPACITY_MULTIPLE;
char **new_strings = realloc(vec->strings, sizeof(char*) * new_capacity);
if (!new_strings)
return 0;
vec->strings = new_strings;
vec->capacity = new_capacity;
}
vec->strings[vec->length] = strdup(str);
if (!vec->strings[vec->length])
return 0;
++vec->length;
return 1;
}
仅在需要时扩展缓冲区更有意义,因此我移动了检查。
我还添加了错误检查。
提示:您可以使用-fsanitize=address
查明错误。
$ gcc -Wall -Wextra -pedantic -fsanitize=address -g main.c vec.c -o a && ./a
==2751==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000092 at pc 0x7f04406a63a6 bp 0x7fffd1a2ada0 sp 0x7fffd1a2a548
WRITE of size 6 at 0x602000000092 thread T0
#0 0x7f04406a63a5 (/usr/lib/x86_64-linux-gnu/libasan.so.4+0x663a5)
#1 0x7f0441a01230 in my_vec_push /.../vec.c:32 <--------
#2 0x7f0441a00dcb in main /.../main.c:13
#3 0x7f0440261b96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
#4 0x7f0441a00c89 in _start (/.../a+0xc89)
[...]
两个错误:
One:
在复制循环中,您分配的字符串缓冲区足够大,以容纳作为参数传递的字符串,而不是要写入的字符串。替换此内容:
new_strings[i] = malloc(sizeof(char) * strlen(str) + 1);
有了这个:
new_strings[i] = malloc(sizeof(char) * strlen(vec->strings[i]) + 1);
Two:
这会分配一个大小错误的缓冲区:
char **new_strings = malloc(
sizeof(char*)
*
vec->capacity + VEC_CAPACITY_MULTIPLE
);
那里的表达式等效于
(sizeof(char*) * vec->capacity) + VEC_CAPACITY_MULTIPLE
但您希望:
sizeof(char*) * (vec->capacity + VEC_CAPACITY_MULTIPLE)
正如池上所指出的,你做了很多不必要和/或效率低下的事情。例如,您的整个容量扩展代码可以缩短为:
if (vec->length == vec->capacity) {
vec->strings = realloc(vec->strings, sizeof(char*) * (vec->capacity + VEC_CAPACITY_MULTIPLE));
vec->capacity += VEC_CAPACITY_MULTIPLE;
}
但是,强烈建议您根据NULL
检查malloc
(和realloc
,如果您使用我的代码)的返回值。