(C) realloc数组修改items指向的数据
你好,
我想分享一个不错的奇怪错误 ;-) 需要一些初步解释:
首先,我有一种字符串类型,PString
它保存它们的大小(和一个哈希值),然后是一个带有字节的灵活数组成员。这是构造函数的类型和种类(最后的 printfl 语句是调试):
typedef struct {
size_t size;
uint hash;
char bytes[];
} PString;
// offset from start of pstring struct to start of data bytes:
static const size_t PSTRING_OFFSET = sizeof(size_t) + sizeof(uint);
PString * pstring_struct (string str, size_t size, uint hash) {
// memory zone
char *mem = malloc(PSTRING_OFFSET + size * sizeof(char));
check_mem(mem);
// string data bytes:
memcpy(mem + PSTRING_OFFSET, str, size);
mem[PSTRING_OFFSET + size] = NUL;
// pstring struct:
PString * pstr = (PString *) mem;
pstr->size = size;
pstr->hash = hash;
printfl("*** str:'%s' (%u) --> pstr:'%s' (%u) 0x%X",
str, size, pstr->bytes, pstr->size, pstr); ///////////////////////
return pstr;
}
[欢迎对这个结构发表任何评论:我完全不确定是否做对了,在这里。这是我第一次使用灵活的数组成员,我找不到在分配的结构中使用它们的示例。]
其次,这些 pstrings 存储在字符串池中,这意味着一组实现为哈希表。像往常一样,用于冲突的“桶”(在哈希和取模之后)是单元的简单链表,每个单元都包含一个 pstring指针和一个指向下一个单元的指针。唯一的特殊细节是单元格本身存储在一个数组中,而不是在堆上的任何地方分配 [1]。希望图片清晰。这是 的定义Cell
:
typedef struct SCell {
PString * pstr;
struct SCell * next;
} Cell;
一切似乎都运行良好,包括对池本身的一系列测试。现在,在测试 pstring 例程(搜索)时,我注意到一个字符串发生了变化。经过一番研究,我终于猜到问题与池增长有关,并且最终可以完全减少围绕单元阵列增长的问题(因此,在将单元重新分配到列表之前)。以下是围绕此增长的调试打印行,show_pool
生成输出的例程副本(仅显示字符串)和输出本身:
static void pool_grow (StringPool * pool, uint n_new) {
...
// Grow arrays:
show_pool(pool); /////////////////////
pool->cells = realloc(pool->cells, pool->n_cells * sizeof(Cell));
check_mem(pool->cells);
show_pool(pool); ////////////////////
...
static void show_pool (StringPool * pool) {
if (pool->n == 0) {
printfl("{}");
return;
}
printf("pool : {\"%s\"", pool->cells[0].pstr->bytes);
PString * pstr;
uint i;
for (i = 1; i < pool->n; i++) {
pstr = pool->cells[i].pstr;
printf(", \"%s\"", pstr->bytes);
}
printl("}");
}
// output:
pool : {"", "abc", "b", "abcXXXabcXXX"}
pool : {"", "abc", "b", "abcXXXabcXXXI"}
如您所见,存储的最后一个字符串有一个额外的字节“I”。由于同时我只是调用 realloc,我发现自己在进一步调试时有点受阻;努力思考无助于揭开这个谜团。(请注意,单元格只保存 pstring指针,那么增长单元格数组如何改变字符串字节?)另外,我被以下事实吓到了,因为 printf 在神秘的“I”之后似乎有一个非常方便的 NUL停在那里。
谢谢你。你能帮我吗?
[1] 这里没有特别的理由这样做,使用字符串池。我通常这样做是为了免费获得一个有序的集合或地图,以及参考的位置。(唯一的开销是除了桶数组之外,单元数组必须增长,但可以通过预先确定维度来减少增长的数量。)