我正在试验malloc和realloc,并为以下问题提出了一些代码:
我想创建一个未知大小的字符串,而不设置任何限制。我可以问 字符 nr 的用户,但我宁愿将 str 调整为用户的大小 键入每个字符。
所以我试图用malloc + realloc来做到这一点,这个想法是每次用户输入一个新的字符时,我都会使用realloc请求+1个内存来保存char。
在尝试实现这一点时,我犯了一个错误,最终做了以下事情:
int main () {
/* this simulates the source of the chars... */
/* in reality I would do getch or getchar in the while loop below... */
char source[10];
int i, j;
for (i=0, j=65; i<10; i++, j++) {
source[i] = j;
}
/* relevant code starts here */
char *str = malloc(2 * sizeof(char)); /* space for 1 char + ' ' */
int current_size = 1;
i = 0;
while(i<10) {
char temp = source[i];
str[current_size-1] = temp;
str[current_size] = ' ';
current_size++;
printf("new str = '%s' | len = %dn", str, strlen(str));
i++;
}
printf("nstr final = %sn", str);
return 0;
}
请注意,realloc 部分尚未实现。
我编译并执行了这段代码,得到了以下输出
new str = 'A' | len = 1
new str = 'AB' | len = 2
new str = 'ABC' | len = 3
new str = 'ABCD' | len = 4
new str = 'ABCDE' | len = 5
new str = 'ABCDEF' | len = 6
new str = 'ABCDEFG' | len = 7
new str = 'ABCDEFGH' | len = 8
new str = 'ABCDEFGHI' | len = 9
new str = 'ABCDEFGHIJ' | len = 10
我发现这些结果很奇怪,因为我预计程序会崩溃:str 有 2 个字符的空间,并且代码正在向 str 添加超过 2 个字符而不请求更多内存。根据我的理解,这意味着我正在我不拥有的内存中写入,因此它应该给出运行时错误。
所以。。。为什么会这样?
(编译器是GCC 4.3.4。
提前谢谢。
编辑:其中一位评论者建议调用 free(( 可能会导致错误被发出信号。我尝试用上面的代码调用free((,执行代码没有导致错误。但是,在向源数组添加更多项并调用 free 后,获得了以下错误:
* 检测到 glibc ./prog: free((: 无效 下一个大小 (快速(: 0x09d67008 **
由于写入的内存超过分配的内存,因此代码具有未定义的行为。
代码碰巧没有崩溃一次(甚至多次(这一事实并没有改变这一点。
未定义的行为并不意味着代码必须崩溃。在您的情况下,恰好有一些内存紧跟在 str
之后,您正在覆盖这些内存。覆盖该内存的实际效果尚不清楚(您可能会更改其他变量的值,破坏堆,发动核打击等(。
似乎来自glibc-2.14,内存分配将按如下大小分配,并且它将设置边框,因此当您分配2字节大小" char *str = malloc(2 * sizeof(char(("时,似乎分配的内存不小于16字节,因此您可能会添加更多项目,然后导致程序错误。
struct _bucket_dir bucket_dir[] = {
{ 16, (struct bucket_desc *) 0},
{ 32, (struct bucket_desc *) 0},
{ 64, (struct bucket_desc *) 0},
{ 128, (struct bucket_desc *) 0},
{ 256, (struct bucket_desc *) 0},
{ 512, (struct bucket_desc *) 0},
{ 1024, (struct bucket_desc *) 0},
{ 2048, (struct bucket_desc *) 0},
{ 4096, (struct bucket_desc *) 0},
{ 0, (struct bucket_desc *) 0}}; /* End of list marker */
void *malloc(unsigned int len)
{
struct _bucket_dir *bdir;
struct bucket_desc *bdesc;
void *retval;
/*
* First we search the bucket_dir to find the right bucket change
* for this request.
*/
for (bdir = bucket_dir; bdir->size; bdir++)
if (bdir->size >= len)
break;
if (!bdir->size) {
printk("malloc called with impossibly large argument (%d)n",
len);
panic("malloc: bad arg");
}
/*
* Now we search for a bucket descriptor which has free space
*/
cli(); /* Avoid race conditions */
for (bdesc = bdir->chain; bdesc; bdesc = bdesc->next)
if (bdesc->freeptr)
break;
/*
* If we didn't find a bucket with free space, then we'll
* allocate a new one.
*/
if (!bdesc) {
char *cp;
int i;
if (!free_bucket_desc)
init_bucket_desc();
bdesc = free_bucket_desc;
free_bucket_desc = bdesc->next;
bdesc->refcnt = 0;
bdesc->bucket_size = bdir->size;
bdesc->page = bdesc->freeptr = (void *) cp = get_free_page();
if (!cp)
panic("Out of memory in kernel malloc()");
/* Set up the chain of free objects */
for (i=PAGE_SIZE/bdir->size; i > 1; i--) {
*((char **) cp) = cp + bdir->size;
cp += bdir->size;
}
*((char **) cp) = 0;
bdesc->next = bdir->chain; /* OK, link it in! */
bdir->chain = bdesc;
}
retval = (void *) bdesc->freeptr;
bdesc->freeptr = *((void **) retval);
bdesc->refcnt++;
sti(); /* OK, we're safe again */
return(retval);
}
除了您触发未定义的行为(包括但不强制要求"崩溃"(之外,我认为您的应用程序实际上确实拥有您正在写入的内存。
在现代操作系统上,内存以页为单位处理,内存块大于两个字节。AFAIK malloc 要求操作系统提供完整的页面,并在需要时在内部划分它们。(注意:取决于实现,但我认为至少 glibc 以这种方式运行。因此,操作系统允许您写入内存,因为从技术上讲,它是您的。在内部,malloc 通常会划分页面并在每个请求时提供部分页面。因此,您可能会覆盖堆上的另一个变量。或者超出界限的写入,根据malloc的观点,内存仍在等待请求。
仅当您尝试写入尚未从操作系统分配或标记为只读的页面时,我才会崩溃。
[将行为未定义的事实用于此类操作]
在调用 realloc 或 free 时通常会检查堆的完整性,而不是在每次写入时,您可能没有覆盖足够多以导致崩溃。
请注意,您最后没有免费通话,如果您愿意,您可能会崩溃。
补充上一个答案,真的没有空间容纳 2,它只是内存中的一个指针。在某个地方,malloc 记得它为 2 个字符提供了空间,但这是 malloc 的内部工作。
您可以尝试以下小实验,看看它是如何工作的:
在第一个指针的后面创建另一个指针。
char *str2 = str + 5;
/* or you could simply malloc another */
char *str2 = malloc(2);
printf("str=%d, str2=%dn",str,str2);
/* to eyeball the pointers actually received
and note the difference in the two pointers.
You will need to raise source length to at least
that much to see the results below
*/
并在第一个 printf 之后在循环中引入另一个 printf:
printf("new str2 = '%s' | len = %dn", str2, strlen(str2));
迟早 str2 也会开始显示相同的字母。
呵呵