C printf更改函数内部变量的内容

时间:2019-02-10 10:34:11

标签: c gcc

所以我正在编写一个解析路径的小函数,它看起来像这样:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int parse_path() {
    char *pathname = "this/is/a/path/hello";
    int char_index = 0;
    char current_char = pathname[char_index];
    char *buffer = malloc(2 * sizeof(char));
    char *current_char_str = malloc(2 * sizeof(char));

    while (current_char != '\0' && (int)current_char != 11) {
        if (char_index == 0 && current_char == '/') {
            char_index++; current_char = pathname[char_index];
            continue;
        }

        while (current_char != '/' && current_char != '\0') {
            current_char_str[0] = current_char;
            current_char_str[1] = '\0';

            buffer = (char *)realloc(buffer, (strlen(buffer) + 2) * sizeof(char));
            strcat(buffer, current_char_str);

            char_index++; current_char = pathname[char_index];
        }

        if (strlen(buffer)) {
            printf("buffer(%s)\n", buffer);
            current_char_str[0] = '\0';
            buffer[0] = '\0';
        }

        char_index++; current_char = pathname[char_index];
    }
};

int main(int argc, char *argv[]) {
    parse_path();
    printf("hello\n");
    return 0;
}

现在,我的代码中存在未定义的行为,看来main方法中的printf调用正在更改buffer变量...如您所见,该程序的输出为:

buffer(this)
buffer(is)
buffer(a)
buffer(path)
buffer(hello)
buffer(buffer(%s)
)
buffer(hello)
hello

我看过其他提到相同问题的文章,人们告诉我使用static char数组等,但这似乎无济于事。 有什么建议吗?

由于某种原因,在此程序中,"hello"的{​​{1}}字符串有时出现在我的printf变量中。

3 个答案:

答案 0 :(得分:4)

塞巴斯蒂安,如果您在@PaulOgilvie回答之后仍遇到问题,则很可能是由于不了解他的回答。您的问题是由于buffer分配,但未初始化。当您调用malloc时,它将分配至少一个请求大小的块,并返回一个指向新块起始地址的指针-但对新块的内容不执行任何操作-表示该块是完全随机值,恰好在新块的地址范围内。

因此,当您第一次调用strcat(buffer, current_char_str);时,buffer中除了随机垃圾而且没有 nul-termination 字符之外,什么都没有-您确实会调用 Undefined行为。 (在buffer中没有字符串结尾)

要解决该错误,只需将buffer设置为空字符串,只需将第一个字符设置为 nul-termination ,即可将其设为空字符串字符,或使用calloc来分配块,以确保所有字节均设置为零。

例如:

int parse_path (const char *pathname)
{
    int char_index = 0, ccs_index = 0;
    char current_char = pathname[char_index];
    char *buffer = NULL;
    char *current_char_str = NULL;

    if (!(buffer = malloc (2))) {
        perror ("malloc-buffer");
        return 0;
    }
    *buffer = 0;    /* make buffer empty-string, or use calloc */
    ...

也不要对路径或数字(包括02进行硬编码,但是现在让它们滑动)。在"this/is/a/path/hello"中对parse_path()进行硬编码是一个非常无用的功能。相反,将您的pathname变量作为参数,这样我就可以采用您要发送给它的任何路径...

虽然realloc一次包含2个字符的整个想法效率很低,但是您始终需要realloc使用临时指针,而不是指针本身。为什么? realloc可以并且确实失败,当失败时,它返回NULL。如果您使用的是指针本身,则在发生故障时将用NULL覆盖当前的指针地址,从而使地址永久丢失到现有内存块中,从而造成内存泄漏。相反,

            void *tmp = realloc (buffer, strlen(buffer) + 2);
            if (!tmp) {
                perror ("realloc-tmp");
                goto alldone;           /* use goto to break nested loops */
            }
            ...
    }
    alldone:;

    /* return something meaningful, your function is type 'int' */
}

一个包含修复程序和临时指针的简短示例为:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int parse_path (const char *pathname)
{
    int char_index = 0, ccs_index = 0;
    char current_char = pathname[char_index];
    char *buffer = NULL;
    char *current_char_str = NULL;

    if (!(buffer = malloc (2))) {
        perror ("malloc-buffer");
        return 0;
    }
    *buffer = 0;    /* make buffer empty-string, or use calloc */

    if (!(current_char_str = malloc (2))) {
        perror ("malloc-current_char_str");
        return 0;
    }

    while (current_char != '\0' && (int) current_char != 11) {
        if (char_index == 0 && current_char == '/') {
            char_index++;
            current_char = pathname[char_index];
            continue;
        }

        while (current_char != '/' && current_char != '\0') {
            current_char_str[0] = current_char;
            current_char_str[1] = '\0';

            void *tmp = realloc (buffer, strlen(buffer) + 2);
            if (!tmp) {
                perror ("realloc-tmp");
                goto alldone;
            }
            strcat(buffer, current_char_str);

            char_index++;
            current_char = pathname[char_index];
        }

        if (strlen(buffer)) {
            printf("buffer(%s)\n", buffer);
            current_char_str[0] = '\0';
            buffer[0] = '\0';
        }

        if (current_char != '\0') {
            char_index++;
            current_char = pathname[char_index];
        }
    }
    alldone:;

    return ccs_index;
}

int main(int argc, char* argv[]) {

    parse_path ("this/is/a/path/hello");
    printf ("hello\n");

    return 0;
}

注意:),您的逻辑在上面已经相当折磨了,您可以使用PATH_MAX大小的固定缓冲区(包括limits.h)并省去分配。否则,您可以应该为buffer分配一些预期数量的字符,例如strlen (pathname),这样可以确保每个路径组件有足够的空间而无需重新分配。我宁愿分配1000个字符,也不愿意加索引担心一次重新分配2个字符...)

使用/输出示例

> bin\parsepath.exe
buffer(this)
buffer(is)
buffer(a)
buffer(path)
buffer(hello)
hello

无需分配的更直接的方法

只需使用大小为PATH_MAX的缓冲区或分配的大小至少为strlen (pathname)的缓冲区,您就可以简单地逐步降低字符串,而无需进行任何重新分配,例如

#include <stdio.h>
#include <limits.h>  /* for PATH_MAX - but VS doesn't provide it, so we check */

#ifndef PATH_MAX
#define PATH_MAX  2048
#endif

void parse_path (const char *pathname)
{
    const char *p = pathname;
    char buffer[PATH_MAX], *b = buffer;

    while (*p) {
        if (*p == '/') {
            if (p != pathname) {
                *b = 0;
                printf ("buffer (%s)\n", buffer);
                b = buffer;
            }
        }
        else
            *b++ = *p;
        p++;
    }
    if (b != buffer) {
        *b = 0;
        printf ("buffer (%s)\n", buffer);
    }
}

int main (int argc, char* argv[]) {

    char *path = argc > 1 ? argv[1] : "this/is/a/path/hello";
    parse_path (path);
    printf ("hello\n");

    return 0;
}

使用/输出示例

> parsepath2.exe
buffer (this)
buffer (is)
buffer (a)
buffer (path)
buffer (hello)
hello

> parsepath2.exe another/path/that/ends/in/a/filename
buffer (another)
buffer (path)
buffer (that)
buffer (ends)
buffer (in)
buffer (a)
buffer (filename)
hello

现在,您可以将要解析的任何路径作为自变量传递到程序,并且无需进行任何更改或重新编译即可对其进行解析。仔细研究一下,如果您有任何疑问,请告诉我。

答案 1 :(得分:2)

strcatbuffer的东西,但buffer尚未初始化。 strcat将首先搜索第一个空字符,然后复制字符串以在那里串联。您现在可能正在覆盖不属于您的内存。

在外部while循环之前,请执行以下操作:

    *buffer= '\0';

答案 2 :(得分:1)

您的代码中存在两个主要问题:

  • malloc()分配的数组未初始化,因此在设置strlen(buffer)指向数组的空终止符之前,调用buffer时会出现不确定的行为。该程序可能只是崩溃,但是在您的情况下,内存块中存在任何内容,之后将其保留到第一个空字节。
  • 在外循环结束之前,如果当前字符是'/',则仅应从路径中获取下一个字符。在您的情况下,您跳过空终止符,并且在读取字符串常量末尾时,程序具有未定义的行为。实际上,解析将通过另一个字符串常量"buffer(%s)\n"和另一个"hello"继续进行。字符串常量似乎是相邻的,没有在系统上填充,这只是一个巧合。

这是更正的版本:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void parse_path(const char *pathname) {
    int char_index = 0;
    char current_char = pathname[char_index];
    char *buffer = calloc(1, 1);
    char *current_char_str = calloc(1, 1);

    while (current_char != '\0' && current_char != 11) {
        if (char_index == 0 && current_char == '/') {
            char_index++; current_char = pathname[char_index];
            continue;
        }
        while (current_char != '/' && current_char != '\0') {
            current_char_str[0] = current_char;
            current_char_str[1] = '\0';

            buffer = (char *)realloc(buffer, strlen(buffer) + 2);
            strcat(buffer, current_char_str);

            char_index++; current_char = pathname[char_index];
        }
        if (strlen(buffer)) {
            printf("buffer(%s)\n", buffer);
            current_char_str[0] = '\0';
            buffer[0] = '\0';
        }
        if (current_char == '/') {
            char_index++; current_char = pathname[char_index];
        }
    }
}

int main(int argc, char *argv[]) {
    parse_path("this/is/a/path/hello");
    printf("hello\n");
    return 0;
}

输出:

buffer(this)
buffer(is)
buffer(a)
buffer(path)
buffer(hello)
hello

但是请注意一些尚存的问题:

  • 分配失败未经测试,导致行为未定义,
  • 分配的块无法释放,导致内存泄漏
  • 不清楚为什么要测试current_char != 11:您是要停在TAB还是换行符?

这里是一个更简单的版本,具有相同的行为:

#include <stdio.h>
#include <string.h>

void parse_path(const char *pathname) {
    int i, n;

    for (i = 0; pathname[i] != '\0'; i += n) {
        if (pathname[i] == '/') {
            n = 1;  /* skip path separators and empty items */
        } else {
            n = strcspn(pathname + i, "/");  /* get the length of the path item */
            printf("buffer(%.*s)\n", n, pathname + i);
        }
    }
}

int main(int argc, char *argv[]) {
    parse_path("this/is/a/path/hello");
    printf("hello\n");
    return 0;
}