【问题标题】:Resizing a char* on the fly - why does this code *work*? [duplicate]即时调整 char* 的大小 - 为什么此代码*有效*? [复制]
【发布时间】:2012-05-23 17:55:36
【问题描述】:

我正在试验 malloc 和 realloc 并想出了一些代码来解决以下问题:

我想创建一个未知大小的字符串,不设置任何限制。我可以问 用户获取 nr 个字符,但我宁愿将 str 调整为用户 键入每个字符。

所以我试图用 malloc + realloc 来做这件事,我的想法是每次用户输入一个新的字符时,我使用 realloc 请求 +1 块内存来保存字符。

在尝试实现这一点时,我犯了一个错误并最终执行了以下操作:

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 + '\0' */
    int current_size = 1;

    i = 0;
    while(i<10) {
            char temp = source[i];
            str[current_size-1] = temp;
            str[current_size] = '\0';
            current_size++;
            printf("new str = '%s' | len = %d\n", str, strlen(str));
            i++;
    }

    printf("\nstr final = %s\n", 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(): invalid next size (fast): 0x09d67008 **

【问题讨论】:

  • sizeof(char) 是 1。你不需要拼出来。
  • 顺便说一下,当实现这样的东西时,从一个相当大的缓冲区(比如,256 字节左右)开始通常是明智的,因为malloc(1) 对于大多数分配器来说效率不高。此外,通常值得避免过于频繁地调用realloc(),因此经常使用在每次重新分配时加倍 缓冲区大小的策略。当然,您的程序可能不会对性能至关重要,但我想指出这一点。

标签: c pointers malloc realloc


【解决方案1】:

由于您正在写入超过分配的内存,因此您的代码有undefined behaviour

代码碰巧没有崩溃一次(甚至多次)的事实并没有改变这一点。

未定义的行为并不意味着代码必须崩溃。在您的情况下,恰好在str 之后紧跟着一些内存,您正在覆盖这些内存。覆盖该内存的实际效果未知(您可能正在更改某些其他变量的值、破坏堆、启动核打击等)。

【讨论】:

  • +1 感谢未定义行为链接。
  • 我将此回复标记为正确的 awnser,因为它是第一个提到行为未定义及其后果的回复。
【解决方案2】:

似乎从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);
}

【讨论】:

  • +1,非常有趣的信息。
  • 我总是实现这样的东西以避免频繁调用 relloc。
【解决方案3】:

除了您触发未定义行为(包括但不强制“崩溃”)这一事实之外,我认为您的应用程序实际上确实拥有您正在写入的内存。

在现代操作系统上,内存在pages 中处理,内存块大于两个字节。 AFAIK malloc 向操作系统询问完整页面,并在需要时在内部划分它们。 (注意:依赖于实现,但我认为至少 glibc 以这种方式运行。)所以操作系统允许你写入内存,因为它在技术上是你的。在内部,malloc 通常会划分页面并在每次请求时提供部分页面。因此,您可能会覆盖堆上的另一个变量。或者越界写入,根据 malloc 的观点,内存仍在等待请求。

我预计只有当您尝试写入尚未从操作系统分配或标记为只读的页面时才会发生崩溃。

【讨论】:

    【解决方案4】:

    [认为这种操作的行为是未定义的]

    在调用 realloc 或 free 时经常检查堆的完整性,而不是在每次写入时检查,您可能没有覆盖到足以导致崩溃的程度。

    请注意,您最后没有免费调用,如果您愿意,您可能会遇到崩溃。

    【讨论】:

    • +1,很有趣。我尝试使用上面的代码免费调用。没有错误。我将源数组大小从 10 更改为 20,也称为 free。错误!编辑原始帖子以反映这一点。
    【解决方案5】:

    补充上一个答案,真的没有空间2,它只是内存中的一个指针。在某个地方,malloc 记得它为 2 个字符分配了空间,但这是 malloc 的内部工作。

    您可以尝试以下小实验,看看它是如何工作的:

    在第一个指针后面创建另一个指针。

    char *str2 = str + 5;
    
    /* or you could simply malloc another */
    
    char *str2 = malloc(2);
    
    printf("str=%d, str2=%d\n",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("new str2 = '%s' | len = %d\n", str2, strlen(str2));
    

    str2 迟早也会开始显示相同的字母。

    HTH

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-26
      • 1970-01-01
      • 2012-11-04
      • 1970-01-01
      • 2017-04-20
      • 1970-01-01
      相关资源
      最近更新 更多