Выделение памяти для простой конкатенации переменных строк в C - PullRequest
1 голос
/ 07 ноября 2010

У меня есть следующая тестовая функция для копирования и конкатенации переменного числа строковых аргументов, автоматически выделяемых:

char *copycat(char *first, ...) {
    va_list vl;
    va_start(vl, first);
    char *result = (char *) malloc(strlen(first) + 1);
    char *next;
    strcpy(result, first);
    while (next = va_arg(vl, char *)) {
        result = (char *) realloc(result, strlen(result) + strlen(next) + 1);
        strcat(result, next);
    }
    return result;
}

Проблема в том, если я сделаю это:

puts(copycat("herp", "derp", "hurr", "durr"));

он должен распечатать 16-байтовую строку, "herpderphurrdurr". Вместо этого он печатает 42-байтовую строку, которая является правильными 16 байтами плюс еще 26 байтов ненужных символов.

Я пока не совсем уверен, почему. Есть идеи?

Ответы [ 5 ]

5 голосов
/ 07 ноября 2010

Вам нужен часовой маркер в вашем списке:

puts(copycat("herp", "derp", "hurr", "durr", NULL));

В противном случае va_arg на самом деле не знает, когда остановиться. Тот факт, что вы получаете мусор, является чистой случайностью, так как вы вызываете неопределенное поведение. Например, когда я запустил ваш код как есть, у меня возникла ошибка сегментации.

Функции переменных аргументов, такие как printf, нуждаются в некотором указании относительно того, сколько элементов передано: printf сам использует строку формата заранее, чтобы выяснить это.

Два основных метода - это число (или строка формата), которое полезно, когда вы не можете использовать одно из возможных значений в качестве часового (маркер в конце).

Если вы можете использовать часовой (например, NULL в случае указателей или -1 в случае неотрицательных целых чисел со знаком, это обычно лучше, поэтому вам не нужно считать элементы) (и возможно вывести количество элементов и список элементов из шага).


Имейте в виду, что puts(copycat("herp", "derp", "hurr", "durr")); - это утечка памяти, поскольку вы выделяете память, а затем теряете указатель на нее. Использование:

char *s = copycat("herp", "derp", "hurr", "durr");
puts(s);
free (s);

- это один из способов исправить это, и вы можете добавить код проверки ошибок в случае неудачного распределения.

5 голосов
/ 07 ноября 2010

Функции списка переменных-аргументов волшебным образом не знают, сколько существует аргументов, поэтому вы, скорее всего, будете ходить по стеку, пока не получите NULL.

Вам либо нужен аргумент numStrings, либо указывайте явный аргумент нулевого терминатора после списка строк.

3 голосов
/ 07 ноября 2010

Из вашего кода я понимаю, что вы предполагаете, что va_next будет возвращать NULL после того, как каждый аргумент будет «вытолкнут». Это неправильно, поскольку va_next абсолютно не может определить количество аргументов: ваш цикл while будет работать до тех пор, пока значение NULL не будет случайно выбрано.

Решение: либо укажите количество аргументов, либо добавьте вызов вашей функции с дополнительным аргументом «NULL».

PS: если вам интересно, почему printf не требует такого дополнительного аргумента, это потому, что число ожидаемых аргументов выводится из строки формата (число '% flag')

2 голосов
/ 07 ноября 2010

В качестве дополнения к другим ответам вы должны привести NULL к ожидаемому типу при использовании его в качестве аргумента для функции с переменным числом: (char *)NULL. Если NULL определен как 0, тогда вместо него будет храниться int, который будет работать случайно, когда int имеет размер sime в качестве указателя, а NULL представлен всеми битами 0. Но ничего из этого не гарантировано, поэтому вы можете столкнуться со странным поведение, которое трудно отладить при переносе кода или даже при изменении только компилятора.

1 голос
/ 07 ноября 2010

Как уже упоминали другие, va_arg не знает, когда остановиться.Вы должны указать NULL (или какой-либо другой маркер) при вызове функции.Несколько замечаний:

  • Вы должны позвонить free по указателям, которые вы получаете от malloc и realloc.
  • Нет никаких оснований приводить результат malloc или realloc в C.
  • При вызове realloc лучше всего сохранять возвращаемое значение во временной переменной.Если realloc не может перераспределить достаточно памяти, он возвращает NULL, но исходный указатель не освобожден.Если вы используете realloc так, как вы делаете, и не можете перераспределить память, то вы потеряли исходный указатель, и ваш последующий вызов strcat, скорее всего, потерпит неудачу.Вы можете использовать это так:

    char *tmp = realloc(result, strlen(result) + strlen(next) + 1);
    if (tmp == NULL)
    {
        // handle error here and free the memory
        free(result);
    }
    else
    {
        // reallocation was successful, re-assign the original pointer
        result = tmp;
    }
    
...