【问题标题】:Understanding String assignments in C理解 C 中的字符串赋值
【发布时间】:2012-03-01 13:17:58
【问题描述】:

好的,我已经阅读了关于 SO 和许多其他地方的大量答案,但我似乎无法掌握这个简单的功能。请原谅我这么简单的事情,我已经 8 年多没有写过 c/c++ 代码了,我非常想重新学习,所以请耐心等待......

我尝试了许多不同的方法来做到这一点,从通过函数参数分配字符串,将值转移到直接返回它,但在此期间似乎没有任何效果。在编译期间我也没有收到任何错误,但我在运行时确实遇到了段错误。我非常想知道为什么下面的函数不起作用......我只是不明白为什么 else 会以 char *content 类型返回正常,但是 strcat(content, line);才不是。尽管 strcat 的手册页显示 strcat 的定义应该是 (char *DEST, const char *SRC)。正如我目前所理解的那样,它试图在 while 内对 line 变量进行强制转换只会将整数返回给指针。所以我被难住了,希望有时间的人能教育一下!

char * getPage(char *filename) {
    FILE *pFile;
    char *content;
    pFile = fopen(filename, "r");
    if (pFile != NULL) {
        syslog(LOG_INFO,"Reading from:%s",filename);
        char line [256];
        while (fgets(line, sizeof line, pFile) != NULL) {
            syslog(LOG_INFO,">>>>>>>Fail Here<<<<<<<");
            strcat(content, line);
        }
        fclose(pFile);
    } else {
        content = "<!DOCTYPE html><html lang=\"en-US\"><head><title>Test</title></head><body><h1>Does Work</h1></body></html>";
        syslog(LOG_INFO,"Reading from:%s failed, serving static response",filename);
    }
    return content;
}

非常感谢这篇文章中的所有出色答案。我会给讨论中的每个人打勾,但不幸的是我不能……

【问题讨论】:

    标签: c string char return strcat


    【解决方案1】:

    这很简单,但如果您习惯了高级语言,这将非常令人惊讶。 C 不会为你管理内存C 并没有真正的字符串content 变量是指针,而不是字符串。在调用strcat 之前,您必须手动分配字符串所需的空间。编写这段代码的正确方法是这样的:

    FILE *fp = fopen(filename, "r");
    if (!fp) {
        syslog(LOG_INFO, "failed to open %s: %s", filename, strerror(errno));
        return xstrdup("<!DOCTYPE html><html lang=\"en-US\"><head><title>Test</title>"
                      "</head><body><h1>Does Work</h1></body></html>");
    } else {
        size_t capacity = 4096, offset = 0, n;
        char *content = xmalloc(capacity);
        size_t n;
        while ((n = fread(content + offset, 1, capacity - offset, fp)) > 0) {
            offset += n;
            if (offset == capacity) {
                capacity *= 2;
                content = xrealloc(content, capacity);
            }
        }
        if (n < 0)
            syslog(LOG_INFO, "read error from %s: %s", filename, strerror(errno));
        content[offset] = '\0';
        fclose(fp);
        return content;
    }
    

    注意事项:

    1. 由 I/O 故障触发的错误消息应始终包含 strerror(errno)
    2. xmallocxreallocxstrdup 是它们对应的包装函数,没有前导 x;他们使程序崩溃而不是返回NULL。这几乎总是比在可能发生的每个地方手动从内存不足中恢复要少的痛苦。
    3. 在打开失败的情况下,我返回xstrdup("...") 而不是"...",以便调用者始终可以调用free(content)。对字符串字面量调用 free 会使您的程序崩溃。
    4. 天哪,工作量很大,不是吗?这就是为什么人们倾向于使用高级语言编写 Web 应用程序的原因。 ;-)

    【讨论】:

    • 我很谦虚;在额外的 19 秒内,你重写了这件事。好的。 :)
    • xmalloc 不在 C 语言中 - 老实说 - 你的内存已经用完了 - 你打算怎么做才能恢复?崩溃是最好的选择。
    • 好的,所以我最简单地遵循了这个答案,因为我看到内容上的内存重新分配。我确实必须将 xmalloc 和 xstrdup 更改为 strdup malloc 和 realloc 但它似乎工作正常。我要拉的页面永远不会是大页面,它们甚至可能永远不会超过 1k。是否存在内存不足等问题@AdrianCornish。
    • @user1198639 在现代机器上,内存不足不太可能成为问题 - 更重要的是您将如何处理它。如果没有剩余内存 - 程序的灾难性故障是最好的选择。
    • @AdrianCornish 由于 malloc 和 realloc 将尝试分配更多内存来保存数据,是否有 prefunc 可以运行来测试并查看是否实际上有足够的内存可用?例如检查可用内存 - 内容指针大小 + (100 * 内容)?我说的是 100 * 内容的大小,因为系统上的其他应用程序可能会同时分配内存,所以我只是以此为例,如果没有更多的内容,则程序会出现错误崩溃?
    【解决方案2】:

    您需要为content 分配内存。按照您的操作方式,它必须足够大以容纳整个文件。您可以预先分配一个巨大的缓冲区并希望获得最好的结果,或者分配一个较小的缓冲区并根据需要重新分配它。

    更好的是重新排列代码以避免一次存储整个文件的需要,尽管如果您的调用者需要将整个网页作为字符串,这可能会很困难。

    另请注意,您需要从两个代码路径返回相同类型的内存。有时不能返回静态字符串,有时不能返回堆分配的字符串。这肯定会让人头疼和/或内存泄漏。因此,如果您将文件内容复制到内存块中,您还应该将静态字符串复制到相同类型的块中。

    【讨论】:

    • 根据 Zack 的示例,我意识到我确实有 2 种不同的类型,尽管我已经完全忘记了这一点,因为我最近一直在研究更高级的语言。不过,该函数本身确实需要返回整个页面,因为它直接从该函数作为对显示的标准页面的拦截提供。如果我确实有一个从页面返回内容的函数,对于分配的少量数据逐行返回会更好吗?除了内存分配还有什么好处呢?
    • 我的评论并不真正适用于您的特定情况,其中所需的输出是单页。但通常,单个巨大的文本缓冲区是一个次优的数据结构。几乎每个操作都变成 O(N)。您需要提前选择一些内存限制并处理潜在的溢出,或者根据需要重新分配,包括复制数据,这又需要 O(N) 时间。通常最好将文本动态解析为更易于管理的数据结构,无论是列表、树、散列还是应用程序需要的任何其他数据结构。
    【解决方案3】:

    content 只是一个指向字符串的指针,而不是实际的字符串——它为您的字符串保留了 0 个字节的空间。您需要分配足够大的内存来保存小时字符串。请注意,之后您必须释放它

    char *content=malloc(256);
    

    你的代码应该没问题 - 哦,我建议使用 strncat

    之前对内容的第二次分配工作正常 - 因为您将指针设置为指向您的 const 字符串。如果您将内容更改为 malloc 的内存区域 - 那么您还希望将您的固定字符串 strncpy 到内容中。

    如果你可以使用 C++ std::string 是理想的。

    【讨论】:

      【解决方案4】:

      char *foo 只是一个指向某块内存的指针,它保存着构成字符串的字符。所以你不能使用strcat,因为你没有任何内存可以复制到。在if 语句中,您正在使用包含该行的char line[256] 在堆栈上分配本地内存,但是由于该内存是函数的本地内存,因此一旦返回,它将消失,因此您不能return line;

      所以你真正想要的是分配一些持久内存,例如使用strdupmalloc,以便您可以从函数中返回它。请注意,您不能混合使用常量和分配的内存(因为您的函数的用户必须free 内存 - 只有当它不是常量时才有可能)。

      所以你可以使用这样的东西:

      char * getPage(const char *filename) {
          FILE *pFile;
          char *content;
          pFile = fopen(filename, "r");
          if (pFile != NULL) {
              syslog(LOG_INFO,"Reading from:%s",filename);
              /* check the size and allocate memory */
              fseek(pFile, 0, SEEK_END);
              if (!(content = malloc(ftell(pfile) + 1))) { /* out of memory ... */ }
              rewind(pFile);
              /* set the content to be empty */
              *content = 0;
              char line [256];
              while (fgets(line, sizeof line, pFile) != NULL) {
                  syslog(LOG_INFO,">>>>>>>Fail Here<<<<<<<");
                  strcat(content, line);
              }
              fclose(pFile);
          } else {
              content = strdup("<!DOCTYPE html><html lang=\"en-US\"><head><title>Test</title></head><body><h1>Does Work</h1></body></html>");
              syslog(LOG_INFO,"Reading from:%s failed, serving static response",filename);
          }
          return content;
      }
      

      这不是最有效的方法(因为strcat 每次都必须找到结尾),但对代码的修改最少。

      【讨论】:

      • 很好的重写,但请注意,在您测量其大小后文件可能会增长,从而导致重复的strcat() 调用不安全和潜在的安全问题。
      • 这不是重写 - 想法是尽可能少地添加。但是,是的,你对不断增长的文件是正确的。诚然,我不会那样做,但我认为解释如何创建动态增长的缓冲区可能太多了——但从接受的答案(这是重写)来看,我猜不是;)
      【解决方案5】:

      较早的答案提出了解决方案:

      char content[256];
      

      这个缓冲区将不足以容纳除最小文件之外的任何内容并且return content; 执行时指针content 超出范围。 (您之前的行,content = "static.."; 很好,因为字符串被放置在 .rodata data segment 中,并且它的指针在程序的整个生命周期中将始终指向相同的数据。)

      如果你用malloc(3)content 分配内存,你可以用realloc(3) 来“增加”所需的空间,但这会引入一个可怕的错误——无论你把指针交给什么都必须清理在完成数据分配内存之后(否则您会泄漏内存),并且它不能简单地调用free(3),因为content 指针可能是静态分配的内存。

      所以,您有两个简单的选择:

      • 每次需要时使用strdup(3)复制静态字符串,并使用content = malloc(size); 作为非静态路径
      • 让你的调用者负责提供内存;每个调用都需要提供足够的内存来处理文件的内容静态字符串。

      我可能更喜欢第一种方法,只是因为在调用之前无法知道第二种方法所需的大小。

      【讨论】:

      • 是的,我错了。我错过了本地 var 的返回 - 我已经更新了堆上存储的答案。
      • 为这个特定的函数释放内存我 100% 没问题,因为我过去曾遇到过一些重大的内存泄漏问题,所以此时我通常会尝试 free(xvar);只是检查并确保它确实需要被释放,因为如果不需要释放它,kdevelop 会大喊大叫,所以我觉得比抱歉更安全,如果它错过了它,应用程序会崩溃而不是导致泄漏。不过这里的问题是指针是否需要在之后为空?
      • 您不需要将已释放的指针设为 NULL,但这样做是一种好习惯 - 这样其他代码就知道它是未分配的。
      • @user1198639:KDevelop 无法总是知道变量是否可以被释放:考虑您的原始版本 getPage() 和以下行:char *to_client = getPage("/etc/passwd"); KDevelop 无法知道是否 @987654337 @ 是一个安全漏洞或防止内存泄漏,因为 to_client 的值取决于运行时条件。 (像Cyclone 这样的语言会禁止这样做,但 C 很乐意让你在脚上开枪。)
      • @sarnold 100% 同意你的观点,因为我不依赖 kdevelop,这是一个有用的功能。由于我仍在重新学习,我只是将 free(varname) 分配给我创建的任何变量等。现在效率不高......但它可以防止愚蠢:) 如果我不需要清理它在堆外,编译器会抱怨或应用程序崩溃,我通常使用 syslog 来指示故障位置,作为在一些小型共享库上调试的一种非常快速的方法。现在编译速度如此之快,我不介意重新编译应用程序和测试......我记得在高中编译一个应用程序需要几个小时:)
      【解决方案6】:

      content 是一个野指针;该变量包含垃圾,因此它指向左侧字段的某个位置。当您使用strcat 将数据复制到其中时,数据会转到某个随机的(可能是错误的)位置。解决这个问题的方法是让content 指向一个好的地方。由于您希望它比您的函数调用寿命更长,因此需要在函数调用堆栈之外的某个地方分配它。您需要使用malloc() 在堆上分配一些空间。然后调用者将拥有该内存,并应在不再需要时调用free() 将其删除。

      您还需要将直接分配给contentelse 部分更改为使用strcpy,以便free() 始终有效。你不能释放你没有分配的东西!

      通过所有这些代码,请确保您记住您为malloc() 分配了多少空间,并且写入的数据不要超过您的空间,否则您会遇到更多的崩溃。

      【讨论】:

        猜你喜欢
        • 2011-09-08
        • 1970-01-01
        • 2012-11-04
        • 2016-04-01
        • 2012-05-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-05-22
        相关资源
        最近更新 更多