C 中的字符串向量打印错误的字符串



我用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,如果您使用我的代码)的返回值。

最新更新