ошибка сегментации внутри цикла - PullRequest
2 голосов
/ 07 февраля 2012

Я человек C #, новичок в языке C, впервые работающий с точками.

У меня есть эта функция, которая в будущем будет работать с malloc(), realloc() и free():

char ** split(char * delimiter, char * input) {

        int i = 0;
        int size = sizeof(char *);
        char ** tokens;
        char * token;
        char * state; 
        tokens = (char **) malloc(size);

        if(tokens == NULL) {
            printf("Allocation failed.");
            return;
        }

        for(token = strtok_r(input, delimiter, &state); 
             token != NULL;
             token =  strtok_r(NULL, delimiter, &state),
             i++, size *= i) {

             tokens = (char **) realloc(tokens, size);

             if(tokens == NULL) {
                printf("Realloc failed.");
                return;
             }

             tokens[i] = state;
        }

        return tokens;
}

когда я звоню:

char * IPNumber = "127.0.01";
char * delimiter = "."; 
char ** parts = split(delimiter, IPNumber);

, это дает segmentation fault.

Я ищу объяснение, как получить (вычислить) значение размера, которое будет использоваться ввторой аргумент функции realloc().Заранее спасибо.

Ответы [ 4 ]

2 голосов
/ 07 февраля 2012

Размеры вашего malloc / calloc неверны (вы умножаете на предполагаемое количество, которое увеличивает массив на count!)

По первому элементу: i = 0, size = sizeof (char *); На втором элементе i = 1, size = sizeof (char) / * слишком мало для двух элементов * /

char ** split(char * delimiter, char * input) {

        unsigned size , used;
        char ** array = NULL;
        char * token;
        char * state; 

        size = used = 0;
        for(token=strtok_r(input, delimiter, &state); token; token=strtok_r(NULL, delimiter, &state) ) {

             if (used+1 >= size) {
                        size = size ? 2*size: 4;
                        array = realloc(array, size * sizeof *array);

                        if (!array) { printf("Realloc failed."); return NULL ; /*leak here*/ }
             }

             array[used++] = state;
        }
        /* NOTE: need a way to communicate the number of elements back to the caller */
        if (array) array[used] = NULL;

        return array;
}

ОБНОВЛЕНИЕ: вот тестовый драйвер

int main(void)
{
char stuff[] = "this is the stuff";
char **ppp;
unsigned idx;

ppp = split( " " , stuff);
for (idx = 0; ppp && ppp[idx]; idx++) {
        fprintf(stdout, "%u: %s\n", idx, ppp[idx] );
        }
return 0;
}
2 голосов
/ 07 февраля 2012

Хорошо, я догадался, что вы намеревались вернуть массив строк:

включает

char ** split(char * delimiter, char * input) {
        int i;
        char ** tokens;
        char * token;
        char * state;

        tokens = (char **) malloc(sizeof(char *) * (2));

        if(tokens == NULL) {
            printf("Allocation failed.");
            return NULL;
        }

        tokens[0]=(char *)1; /* one element populated */
        tokens[1]=NULL; /* no tokens */

        for(i=1, token = strtok_r(input, delimiter, &state);
             token != NULL;
             token =  strtok_r(NULL, delimiter, &state),
             i++) {

             /* grow array by one element - originally made with 2 */
             {
               char **new =(char **) realloc(tokens, (i+2) * sizeof(char *));

                 if(new == NULL) {
                    printf("Realloc failed.");
                    free(tokens);
                    return NULL;
                 }
                 else
                 {
                    tokens = new;
                    tokens[i+1] = NULL;  /* initialize new entry */
                 }
             }

             tokens[i] = token;
             tokens[0] = (char *)i;
        }

        return tokens;
}

int main( void )
{
    char str[] = "129.128.0.1";
    char delim[] = ".";
    char **ret;

    ret = split( delim, str );

    printf( "tokens = %d\n", (int)ret[0] );
    printf( "tokens[1] = %s\n", ret[1] );
    printf( "tokens[2] = %s\n", ret[2] );
    printf( "tokens[3] = %s\n", ret[3] );
    printf( "tokens[4] = %s\n", ret[4] );
    printf( "tokens[5] = %s\n", ret[5] );
}
  1. возвращает явные значения, а не мусор.
  2. изменение функции realloc. Вы увеличиваете массив на один элемент во время каждого цикла.
  3. Исправлена ​​утечка памяти
  4. сохранить значение, возвращаемое strtok_r, а не его частную внутреннюю переменную состояния.
  5. массив на один больше, чем нужно, поэтому убедитесь, что он инициализирован в NULL
  6. ноль массива - это размер, который не должен переполняться, если вы не обрабатываете HUGE строк
0 голосов
/ 07 февраля 2012

Я собирался указать на вещи, которые нужно исправить, но вместо этого просто переписал это следующим образом:

char **split(char *delim, char *input)
{
  char *save;  /* saved state for strtok_r */
  char **tmp,  /* temporary result from realloc (for error handling) */
       **res;  /* result - NULL-terminated array of tokens */
  int i,       /* index of current/last token */ 
      count;   /* number of elements in res (including NULL) */

  /* Allocate first element for res */
  if ( !(res = malloc(sizeof(res[0]))) ) {
    /* return NULL if malloc() fails */
    fprintf(stderr,"split(): malloc() failed\n");
    return NULL;
  }

  /* res[0] = first token, or NULL */
  res[0] = strtok_r(input,delim,&save);

  /* if it was a token, grab the rest.  Last one will be the NULL
   *  returned from strtok_r() */
  if (res[0])
    i = 0;
    count = 1;
    do {
      /* Resize res, for next token */
      /* use a temporary pointer for realloc()'s result, so that
       *  we can check for failure without losing the old pointer */
      if ( tmp = realloc(res, sizeof(res[0]) * ++count) )
        res = tmp;
      else {
        /* if realloc() fails, free res and return NULL */
        free(res);
        fprintf(stderr,"split(): realloc() failed.\n");
        return NULL;
      }
      /* get next token, or NULL */
      res[++i] = strtok_r(NULL,delim,&save);
    } while (res[i]); /* done when last item was NULL */

  return res;
}

Таким образом, размер для realloc - это количество необходимых элементов, умноженное на размер элемента.

Приведенная выше версия кода возвращает массив с нулевым символом в конце.Другой подход заключается в том, чтобы как-то вернуть количество элементов массива (например, с помощью аргумента int * или size_t *);но в любом случае вам нужно, чтобы вызывающая сторона знала, где находится конец массива результатов.

Использование strtok_r() для этого также добавляет еще один улов: исходная строка input равна , а не осталось без изменений.Поэтому вам необходимо помнить об этом и при использовании этой (или исходной) функции - либо используйте ее, когда вам не нужно сохранять исходную строку, либо сначала создайте дубликат оригинала.

0 голосов
/ 07 февраля 2012

Полная перезапись .Существуют некоторые проблемы с исходным кодом в опубликованном виде.

  • Неверное вычисление размера перераспределения.
  • Передача строковой константы в strtok_r недопустима.Он изменяет первый аргумент, что может привести к нарушению доступа при передаче строкового литерала.
  • Назначение токена в массиве результатов начинается с позиции 1 вместо 0.
  • присваивание использует переменную состояния вместо токена (вероятно, совсем не желаемый результат и, возможно, неопределенное поведение).
  • У вызывающей стороны нет возможности узнать, сколько токенов содержится в возвращаемом массиве.
  • Неудачный вызов realloc не освобождает исходный указатель, поэтому он может просочиться.

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

char ** split(char * delimiter, char * input) {
    int size;
    int maxsize;
    char ** tokens;
    char * token;
    char * state;

    // compute max possible tokens, which is half the input length.
    // Add 1 for the case of odd strlen result and another +1 for
    // a NULL entry on the end
    maxsize = strlen( input ) / 2 + 2;
    tokens = (char**)malloc( maxsize * sizeof( char*) );
    if(tokens == NULL) {
        printf("Allocation failed.");
        return NULL;
    }

    size = 0;
    for(token = strtok_r(input, delimiter, &state);
         token != NULL;
         token =  strtok_r(NULL, delimiter, &state) ) {

         tokens[size++] = token;
    }

    assert( size < maxsize );
    // Put a NULL in the last entry so the caller knows how many entries
    // otherwise some integer value would need to be returned as an output
    // parameter.
    tokens[size] = NULL;

    // NOTE: could use realloc from maxsize down to size if desired

    return tokens;
}

Использование может выглядеть следующим образом.Обратите внимание на использование strdup, чтобы избежать передачи строковой константы в функцию:

char * IPNumber = strdup( "127.0.01" );
char * delimiter = ".";
char ** parts = split(delimiter, IPNumber);
int i;

if ( parts ) {
   for ( i = 0; parts[i] != NULL; i++ )
      printf( "%s\n", parts[i] );

   free( parts );
   }

free( IPNumber );
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...