Странное поведение при присвоении символа**[i]

#c

Вопрос:

При отладке простого приложения оболочки я столкнулся со странной ошибкой, когда команда имеет >2 параметров. Я отследил его до этой функции (строка [] — это командная строка, например echo one two , а sep-символ для разделения строки, установленный в ' ' том месте, где она вызывается).:

 char **split(char string[], char *sep) {
    char *token = strtok(string, sep);
    char **argv = calloc(1, sizeof(char*));
    
    int i = 0;
    while (token != NULL) {
        argv = realloc(argv, sizeof(argv)   sizeof(char*));
        argv[i] = calloc(strlen(token), sizeof(char));
        strcpy(argv[i  ], token);
        token = strtok(NULL, sep);
    }
    argv[i] = NULL; // sets argv[0] even if i == 4
    return argv;
}
 

Это прекрасно работает с Однако с 3 параметрами argv[0] в конечном итоге устанавливается значение null в конце вместо последнего элемента.

Я делаю что-то глупое или есть неопределенное поведение?

изменить: >=4 параметра вызывают сбой программы:

 realloc(): invalid next size
signal: aborted (core dumped)
 

Ответ №1:

В вашем коде три ошибки.

  1. strlen() задает длину строки без нулевого терминатора. Так что вам придется писать calloc(strlen(token) 1, sizeof(char)); , чтобы освободить для этого место.
  2. sizeof(argv) возвращает размер указателя (а не количество элементов) и является постоянным во время компиляции.
  3. argv[i] = NULL; это выходит за рамки

Рабочий код:

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

char **split(char string[], char *sep)
{
    char *token = strtok(string, sep);
    char **argv = calloc(1, sizeof(char *));
    int i = 0;
    while(token != NULL)
    {
        argv = realloc(argv, (i   1) * sizeof(char *));
        argv[i] = calloc(strlen(token)   1, sizeof(char));
        strcpy(argv[i  ], token);
        token = strtok(NULL, sep);
    }

    argv = realloc(argv, (i   1) * sizeof(char *));
    argv[i] = NULL;
    return argv;
}


int main(void)
{
    char s[] = "hello world test word";
    char **result = split(s, " ");

    printf("[0] = %sn", result[0]);
    printf("[1] = %sn", result[1]);
    printf("[2] = %sn", result[2]);
    printf("[3] = %sn", result[3]);

    return 1;

}
 

Я рекомендую использовать gdb и valgrind для отладки таких проблем.

Ответ №2:

Проблема заключается в этих двух линиях:

     argv = realloc(argv, sizeof(argv)   sizeof(char*));
    argv[i] = calloc(strlen(token), sizeof(char));
 

sizeof(argv) указывает размер указателя (обычно 4 или 8), а не количество элементов, на которые он указывает. Поэтому, когда вы звоните realloc , вы всегда просите одно и то же количество байтов, которого, оказывается, достаточно для двух элементов массива. Затем, когда у вас больше 2, вы записываете за пределы выделенной памяти, вызывая неопределенное поведение.

Так i как отслеживается длина массива, используйте это при вызове realloc , и вам нужно умножить на размер элемента, а не добавлять.

     argv = realloc(argv, (i   1) * sizeof(char*));
 

В следующей строке вы выделяете достаточно места для символов в строке, но недостаточно для завершающего байта null, поэтому добавьте 1 для этого:

     argv[i] = calloc(strlen(token)   1, sizeof(char));