【问题标题】:strncat looks like its preserving data across calls?strncat 看起来像是跨调用保留数据?
【发布时间】:2013-03-01 20:24:07
【问题描述】:

我发现string.h 标准库中的strncat 函数出现了一些奇怪的行为,希望得到一些帮助以了解发生了什么。


我的问题的症结在于我创建了一个名为readLine 的函数,其目的是将文件的行作为char * 字符串返回,而没有尾随的换行符终止符。该函数如下所示:

char * readLine(FILE * fp) {
    char * chunk = NULL;
    char * line = NULL;
    int count = 0;

    // iterate through chunks of a line until we reach the end (or an error)
    while (fgets((chunk = malloc(sizeof(char) * BUFFER_SIZE)), BUFFER_SIZE, fp) != NULL) {

        // realloc on a null pointer works like malloc  
        line = realloc(line, ++count * BUFFER_SIZE * sizeof(char));    

        printf("chunk's contents: %s\n", chunk);

        // does chunk contain the end of a line?
        if(strchr(chunk, '\n') == NULL) {   
            // concatenate string parts and continue loop
            strncat(line, chunk, strlen(chunk) + 1);        
            free(chunk);

        } else {
            // we want to return a \0 terminated string without the \n
            // expected position of \n in chunk is ({length of chunk}-1)
            chunk[(strlen(chunk) - 1)] = '\0';

            // concatenate string parts
            strncat(line, chunk, strlen(chunk) + 1);
            printf("readLine line:    %s\n", line);
            free(chunk);

            break;        
        }        
    }
    return line;
}

我在主循环中调用它,如下所示:

FILE * fp = NULL;

if ((fp = fopen(FILE_PATH, "r")) != NULL) {
    char * line = NULL;

    while ((line = readLine(fp)) != NULL) {
        printf("main line:        %s\n\n", line);
        free(line);
    }

    fclose(fp);
}

现在奇怪的行为出现在我对#define BUFFER_SIZE 1000 的定义中。这样设置后,我得到以下输出(这不是我想要的):

chunk's contents: I am on line 1
readLine line:    I am on line 1
main line:        I am on line 1

chunk's contents: Over here I am on line 2
readLine line:    I am on line 1Over here I am on line 2
main line:        I am on line 1Over here I am on line 2

chunk's contents: Line 3 here
readLine line:    I am on line 1Over here I am on line 2Line 3 here
main line:        I am on line 1Over here I am on line 2Line 3 here

chunk's contents: Look out for 4
readLine line:    I am on line 1Over here I am on line 2Line 3 hereLook out for 4
main line:        I am on line 1Over here I am on line 2Line 3 hereLook out for 4

chunk's contents: Johnny 5 alive!
readLine line:    I am on line 1Over here I am on line 2Line 3 hereLook out for 4Johnny 5 alive!
main line:        I am on line 1Over here I am on line 2Line 3 hereLook out for 4Johnny 5 alive!

但是如果我将该定义更改为 #define BUFFER_SIZE 20 之类的东西,我会得到我正在寻找的输出:

chunk's contents: I am on line 1

readLine line:    I am on line 1
main line:        I am on line 1

chunk's contents: Over here I am on l
chunk's contents: ine 2

readLine line:    Over here I am on line 2
main line:        Over here I am on line 2

chunk's contents: Line 3 here

readLine line:    Line 3 here
main line:        Line 3 here

chunk's contents: Look out for 4

readLine line:    Look out for 4
main line:        Look out for 4

chunk's contents: Johnny 5 alive!

readLine line:    Johnny 5 alive!
main line:        Johnny 5 alive!

我认为我的问题已缩小到strncat(line, chunk, strlen(chunk) + 1); 行。我不明白为什么当我的BUFFER_SIZE 足够高时包含前面的lines。

【问题讨论】:

  • 在使用之前尝试将缓冲区设置为 0。
  • 请注意,您的 strncat 正在实现 Schlemiel the Painter 算法:joelonsoftware.com/articles/fog0000000319.html
  • 请注意,如果使用 line = realloc(line, ...,如果 realloc 失败并返回 NULL(您应该检查),您将出现不可恢复的内存泄漏。从 API 的角度来看,realloc 函数很糟糕,其中一个原因是它会导致编写这样的错误代码。

标签: c strcat


【解决方案1】:
line = realloc(line, ++count * BUFFER_SIZE * sizeof(char));

不初始化分配的内存。因此,如果 readLine 中的第一个 realloc 将您在上一次调用中获得的内存块归还给您 - 并非不可能,您可能仍然有旧的内容。

无论如何,对于未初始化的内存,第一个 strncat 可能会调用未定义的行为,因为分配的内存中不需要有 0 字节。

在进入循环之前给line分配一个缓冲区,并在第一个字节写入0

另外,不要使用

line = realloc(line, ++count * BUFFER_SIZE * sizeof(char));

如果realloc 失败,您就会泄漏内存。你应该检查realloc的返回值,

char *temp = realloc(line, ++count * BUFFER_SIZE * sizeof(char));
if (temp == NULL) {
  // Oops
} else {
    line = temp;
}

并且不要在fgets 通话中将malloc 转至chunk

while (fgets((chunk = malloc(sizeof(char) * BUFFER_SIZE)), BUFFER_SIZE, fp) != NULL)

如果malloc 失败,也会调用未定义的行为。 malloc 并在致电fgets 之前检查,

while ((chunk = malloc(sizeof(char) * BUFFER_SIZE)) && fgets(chunk, BUFFER_SIZE, fp) != NULL)

【讨论】:

  • 很好的答案,谢谢。在realloc 成语中,如果temp == NULL 是否适合free(line)?我真的很喜欢这样,而条件修改也是
  • 是的,那么line 仍然指向应为freed 的已分配内存。当realloc 失败时,您在退出前执行哪种诊断和恢复/清理取决于。
【解决方案2】:

不过,您可以坚持使用 realloc 并将缓冲区设置为零。

【讨论】:

    【解决方案3】:

    你的问题在这里:

        line = realloc(line, ++count * BUFFER_SIZE * sizeof(char));    
    

    根据realloc 的手册页:

    "realloc(3) does not guarantee that the additional memory is also
     zero-filled."
    

    "If ptr is NULL, realloc() is identical to a call to malloc() for size bytes."
    

    因此,您获得的任何新内存都可能被非零字节填充,这意味着第一次调用它时,第一个字节可能不会有 0,这意味着 strncat 将被追加分配中的任何垃圾字节。

    【讨论】:

      猜你喜欢
      • 2016-09-02
      • 2011-04-02
      • 2020-12-31
      • 1970-01-01
      • 2020-11-08
      • 1970-01-01
      • 2016-03-09
      • 1970-01-01
      • 2020-01-14
      相关资源
      最近更新 更多