【问题标题】:C: dynamic char-array crashes heapC:动态字符数组崩溃堆
【发布时间】:2013-03-25 01:03:15
【问题描述】:

我又有一个关于 C 的工作原理的问题。(由 VS2012 编译的 ANSI-C)

我正在将独立程序 (.exe) 重构为 .dll。到目前为止,这工作得很好,但是在记录日志时我偶然发现了一些问题。让我解释一下:

原始程序 - 在运行时 - 写入日志文件并将信息打印到屏幕上。由于我的 dll 将在网络服务器上运行,许多人同时访问有

  • 没有真正的机会正确处理日志文件(并在它们之后进行清理)
  • 没有任何人会看到的控制台窗口

所以我的目标是将要放入日志文件或屏幕上的所有内容写入类似字符串的变量(我知道 C 中没有字符串),然后我可以稍后将 requet 传递给调用者(也是一个 dll,但用 C# 编写)。

因为在 C 中这样的事情是不可能的:

char z88rlog;
z88rlog="First log-entry\n";
z88rlog+="Second log-entry\n";

我有两种可能:

  1. char z88rlog[REALLY_HUGE];
  2. 动态分配内存

在我看来,第一种方法是被忽略,因为:

  • 内存的潜在浪费是相当巨大的
  • 我可能仍然需要比 REALLY_HUGE 更多的内存,从而造成缓冲区溢出

这让我有了第二种方式。我已经为此做了一些工作,并提出了两个解决方案,其中任何一个都无法正常工作。

/* Solution 1 */    
void logpr(char* tmpstr)
{
    extern char *z88rlog;
    if (z88rlog==NULL)
    {
        z88rlog=malloc(strlen(tmpstr)+1);
        strcpy(z88rlog,tmpstr);
    }
    else
    {
        z88rlog=realloc(z88rlog,strlen(z88rlog)+strlen(tmpstr));
        z88rlog=strcat(z88rlog,tmpstr);
    }
}

在解决方案 1 中(等于解决方案 2,您会发现)我通过 char tmpstr[255]; 传递我的新日志条目。我的“日志文件”z88rlog 是全局声明的,所以我需要extern 才能访问它。然后我检查是否为z88rlog 分配了内存。如果没有,我会分配我的日志条目大小的内存(我的\0 +1)并将tmpstr 的内容复制到z88rlog。如果是,我会为z88rlog 重新分配内存,其大小为原来的大小+tmpstr 的长度(+1)。然后使用strcat 连接两个“字符串”。使用断点和直接窗口我获得了以下输出:

z88rlog
0x00000000 <Schlechtes Ptr>
z88rlog
0x0059ef80 "start Z88R version 14OS"
z88rlog
0x0059ef80 "start Z88R version 14OS
opening file Z88.DYNÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍýýýý««««««««þîþîþîþ"

显示连续三个logpr调用(断点在strcpy/strcat之前)。最后无法区分的乱码是由于内存分配造成的。之后 VS 发出一条错误消息,指出某些原因导致调试器在realloc.c 中设置断点。因为这显然行不通,所以我炮制了我的绝妙解决方案 2:

/* Solution 2 */
void logpr(char* tmpstr)
{
    extern char *z88rlog;
    char *z88rlogtmp;
    if (z88rlog==NULL)
    {
        z88rlog=malloc(strlen(tmpstr)+1);
        strcpy(z88rlog,tmpstr);
    }
    else
    {
        z88rlogtmp=malloc(strlen(z88rlog)+strlen(tmpstr+1));
        z88rlogtmp=strcat(z88rlog,tmpstr);
        free(z88rlog);
        z88rlog=malloc(strlen(z88rlogtmp)+1);
        memcpy(z88rlog,z88rlogtmp,strlen(z88rlogtmp)+1);
        free(z88rlogtmp);
    }
}

这里我的目标是创建我的日志文件的副本,释放原件的内存为新大小的原件创建新内存并将内容复制回来。并且不要忘记释放临时副本,因为它是通过 malloc 分配的。当它到达free 时会立即崩溃,再次告诉我堆可能已损坏。

所以让我们暂时免费评论。这确实工作得更好 - 让我松了一口气 - 但是在构建日志字符串时突然不是所有来自 z88rlogtmp 的字符都被复制了。但一切仍然正常工作。直到突然我再次被告知堆可能被破坏并且调试器在_heap_alloc (size_t size)in malloc.csize的末尾放置了一个断点 - 根据调试器 - 值为 1041。

所以我有 2 种(或 3 种)方法想要实现这种“字符串增长”,但没有一种有效。给我大小的错误可能会让我得出数组已经变大的结论吗?我希望我能很好地解释我想做什么,有人可以帮助我:-) 提前致谢!

讽刺也许我应该去买一些新的电脑堆。它适合 RAM 插槽吗?谁能推荐一个好品牌? 讽刺

【问题讨论】:

    标签: c char malloc heap-memory realloc


    【解决方案1】:

    你似乎有很多问题。首先,在您的realloc 调用中,您不会为终止'\0' 字符分配空间。在您的第二个解决方案中,您有 strlen(tmpstr+1) 这是不正确的。在您的第二个解决方案中,您还使用strcat 附加到现有缓冲区z88rlog,如果它不够大,您将覆盖未分配的内存,或者覆盖分配给其他东西的数据。 strcat 的第一个参数是目标,这也是函数返回的内容,因此您也释放了新分配的内存。

    第一个解决方案,realloc,应该可以正常工作,如果您只记得分配那个额外的字符。

    【讨论】:

    • 太棒了!修复我的realloc 就成功了!修复 strlen(tmpstr+1) 但没有。这两种解决方案有那么大的不同吗?
    • @lhiapgpeonk 是的,这两种解决方案相同,正如我在更新的答案中所指出的那样。
    【解决方案2】:

    这是解决方案 1 中的一个错误:

    z88rlog=realloc(z88rlog,strlen(z88rlog)+strlen(tmpstr));
    

    因为没有为终止空字符分配空间。请注意,您必须将 realloc() 的结果存储到临时变量中,以避免在失败时发生内存泄漏。更正:

    char* tmp = realloc(z88rlog, strlen(z88rlog) + strlen(tmpstr) + 1);
    if (tmp)
    {
        z88rlog = tmp;
        /* ... */
    }
    

    解决方案 2 中的错误:

    z88rlogtmp=malloc(strlen(z88rlog)+strlen(tmpstr+1));
                                          /*^^^^^^^^^*/
    

    它计算的长度小于tmpstr 的长度。更正:

    z88rlogtmp=malloc(strlen(z88rlog) + strlen(tmpstr) + 1);
    

    指针重新分配导致未定义的行为:

        z88rlogtmp=strcat(z88rlog,tmpstr);
        /* Now, 'z88rlogtmp' and 'z88rlog' point to the same memory. */
    
        free(z88rlog);
        /* 'z88rlogtmp' now points to deallocated memory. */
    
        z88rlog=malloc(strlen(z88rlogtmp)+1);
        /* This call   ^^^^^^^^^^^^^^^^^^ is undefined behaviour,
           and from this point on anything can happen. */
    
        memcpy(z88rlog,z88rlogtmp,strlen(z88rlogtmp)+1);
        free(z88rlogtmp);
    

    此外,如果代码在 Web 服务器中执行,它几乎可以肯定是在多线程环境中运行。由于您有一个全局变量,因此需要同步访问。

    【讨论】:

    • 感谢您的回答!同步访问是什么意思?我的变量在我的 C-DLL 中全局定义。
    • @lhiapgpeonk,如果多个线程试图修改z88rlog,那么你就有了竞争条件。对z88rlog的访问必须被同步化,这意味着任何时候只有线程可以访问修改它。
    • 听起来我可以从任何地方 P/Invoke 到我的 dll 并且我的 dll 只包含一组变量。那是相当可怕的!如果我能从 C# 中的 dll 创建一种对象就好了。你能给我一些关于如何实现这种同步的解释吗?
    【解决方案3】:

    在解决方案 1 中,您需要为终止 NULL 字符分配空间。因此,realloc 应该多包含一个空格,即

    z88rlog=realloc(z88rlog,strlen(z88rlog)+strlen(tmpstr) + 1);
    

    在第二种解决方案中,我不确定z88rlogtmp=strcat(z88rlog,tmpstr);,因为z88rlog 是目标字符串。如果您只想执行malloc,那么

     z88rlogtmp=malloc(strlen(z88rlog)+1 // Allocate a temporary string
     strcpy(z88rlogtmp,z88rlog); // Make a copy
     free(z88rlog); // Free current string
     z88rlog=malloc(strlen(z88rlogtmp)+ strlen(tmpstr) + 1)); //Re-allocate memory
     strcpy(z88rlog, z88rlogtmp); // Copy first string
     strcat(z88rlog, tmpStr);  // Concatenate the next string
     free(z88rlogtmp); // Free the Temporary string
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-02-18
      • 2021-01-02
      • 2013-09-28
      • 2019-08-11
      • 2015-06-05
      • 2015-11-03
      • 1970-01-01
      • 2015-07-27
      相关资源
      最近更新 更多