C printf изменяя содержимое переменной внутри функции - PullRequest
0 голосов
/ 10 февраля 2019

Итак, я пишу небольшую функцию для разбора путей, она выглядит так:

#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;
}

Теперь в моем коде неопределенное поведение, похоже, что вызов printf внутри метода mainменяет переменную buffer ... как вы видите, вывод этой программы:

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

Я смотрел другие посты, где упоминалась та же проблема, и люди рассказывали мнеиспользовать массив static char и т. д., но это, похоже, не помогает.Любые предложения?

Почему-то в этой программе в одно время в моей переменной buffer присутствует строка "hello" из printf.

Ответы [ 3 ]

0 голосов
/ 10 февраля 2019

Себастьян, если после ответа @ PaulOgilvie у вас все еще есть проблемы, то, скорее всего, из-за непонимания его ответа.Ваша проблема связана с тем, что buffer выделено , но не инициализировано .Когда вы вызываете malloc, он выделяет блок по крайней мере запрошенного размера и возвращает указатель на начальный адрес для нового блока - , но ничего не делает с содержимым нового блока -это означает, что блок является полными случайными значениями, которые только что оказались в диапазоне адресов для нового блока.

Поэтому, когда вы вызываете strcat(buffer, current_char_str); в первый раз, и в buffer и ничего, кроме случайного мусора, нетнет нуль-завершающий символ - вы вызываете неопределенное поведение .(в buffer конца строки нет)

Чтобы исправить ошибку, вам просто нужно сделать buffer пустой строкой после ее выделенияустановив первый символ в нуль-завершающий символ или используйте вместо него 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 */
    ...

Также не используйте жесткие коды путей или чисел (включая 0 и 2, но мы пока позволим им скользить).Жесткое кодирование "this/is/a/path/hello" в parse_path() make - довольно бесполезная функция.Вместо этого установите переменную 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;
}

( note: ваша логика довольно замучена выше, и вы могли быпросто используйте фиксированный буфер размером 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

Теперь вы можете пройти любой путьВы хотели бы проанализировать как аргумент для вашей программы, и он будет проанализирован без необходимости что-либо менять или перекомпилировать.Посмотрите вещи и дайте мне знать, если у вас есть вопросы.

0 голосов
/ 11 февраля 2019

В вашем коде есть 2 основные проблемы:

  • массивы, выделенные 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;
}
0 голосов
/ 10 февраля 2019

Вы strcat что-то для buffer, но buffer никогда не инициализировалось.strcat сначала будет искать первый нулевой символ, а затем скопировать строку для конкатенации.Теперь вы, вероятно, перезаписываете память, которая не принадлежит вам.

Перед выполнением внешнего цикла while:

    *buffer= '\0';
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...