2013-03-01 2 views
0

Я вижу некоторое нечетное поведение из функции strncat в стандартной библиотеке string.h и хотел бы, чтобы какая-то помощь понимала, что происходит.strncat выглядит как сохраняющий данные через вызовы?


Суть моего вопроса в функции я создал называется readLine с целью возвращения строк файла как char * строки без завершающего символа новой строки терминатор. Эта функция выглядит следующим образом:

char * readLine(FILE * fp) { 
    char * chunk = NULL; 
    char * line = NULL; 
    int count = 0; 

    // iterate through chunks of a line until we reach the end (or an error) 
    while (fgets((chunk = malloc(sizeof(char) * BUFFER_SIZE)), BUFFER_SIZE, fp) != NULL) { 

     // realloc on a null pointer works like malloc 
     line = realloc(line, ++count * BUFFER_SIZE * sizeof(char));  

     printf("chunk's contents: %s\n", chunk); 

     // does chunk contain the end of a line? 
     if(strchr(chunk, '\n') == NULL) { 
      // concatenate string parts and continue loop 
      strncat(line, chunk, strlen(chunk) + 1);   
      free(chunk); 

     } else { 
      // we want to return a \0 terminated string without the \n 
      // expected position of \n in chunk is ({length of chunk}-1) 
      chunk[(strlen(chunk) - 1)] = '\0'; 

      // concatenate string parts 
      strncat(line, chunk, strlen(chunk) + 1); 
      printf("readLine line: %s\n", line); 
      free(chunk); 

      break;   
     }   
    } 
    return line; 
} 

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

FILE * fp = NULL; 

if ((fp = fopen(FILE_PATH, "r")) != NULL) { 
    char * line = NULL; 

    while ((line = readLine(fp)) != NULL) { 
     printf("main line:  %s\n\n", line); 
     free(line); 
    } 

    fclose(fp); 
} 

Теперь странное поведение приходит в моем определении #define BUFFER_SIZE 1000. С его установить, как я получаю следующий результат (который не то, что я хочу):

chunk's contents: I am on line 1 
readLine line: I am on line 1 
main line:  I am on line 1 

chunk's contents: Over here I am on line 2 
readLine line: I am on line 1Over here I am on line 2 
main line:  I am on line 1Over here I am on line 2 

chunk's contents: Line 3 here 
readLine line: I am on line 1Over here I am on line 2Line 3 here 
main line:  I am on line 1Over here I am on line 2Line 3 here 

chunk's contents: Look out for 4 
readLine line: I am on line 1Over here I am on line 2Line 3 hereLook out for 4 
main line:  I am on line 1Over here I am on line 2Line 3 hereLook out for 4 

chunk's contents: Johnny 5 alive! 
readLine line: I am on line 1Over here I am on line 2Line 3 hereLook out for 4Johnny 5 alive! 
main line:  I am on line 1Over here I am on line 2Line 3 hereLook out for 4Johnny 5 alive! 

НО если я изменить это определение к чему-то вроде #define BUFFER_SIZE 20, я получаю выход я ищу :

chunk's contents: I am on line 1 

readLine line: I am on line 1 
main line:  I am on line 1 

chunk's contents: Over here I am on l 
chunk's contents: ine 2 

readLine line: Over here I am on line 2 
main line:  Over here I am on line 2 

chunk's contents: Line 3 here 

readLine line: Line 3 here 
main line:  Line 3 here 

chunk's contents: Look out for 4 

readLine line: Look out for 4 
main line:  Look out for 4 

chunk's contents: Johnny 5 alive! 

readLine line: Johnny 5 alive! 
main line:  Johnny 5 alive! 

Я думаю, что у меня есть проблемы, суженные до strncat(line, chunk, strlen(chunk) + 1); линии. Я не понимаю, почему предыдущие line s включены, когда мой BUFFER_SIZE достаточно высок.

+0

Попробуйте помешать вашим буферам до 0 перед их использованием. –

+0

Обратите внимание, что ваш 'strncat' реализует алгоритм Painter's Schlemiel: http://www.joelonsoftware.com/articles/fog0000000319.html – ecatmur

+1

Обратите внимание, что с помощью' line = realloc (line, ... 'у вас будет неустранимая память утечка должна быть переполнена и вернуть NULL (что вы должны проверить). Функция 'realloc' ужасна с точки зрения API, среди других причин, потому что она приглашает написать неправильный код, подобный этому. – hlovdal

ответ

4
line = realloc(line, ++count * BUFFER_SIZE * sizeof(char)); 

не инициализирует выделенную память. Так что если первый realloc в readLine возвращает вам кусок памяти, который вы получили в предыдущем вызове, - это невозможно, у вас может быть еще старое содержимое.

В любом случае с неинициализированной памятью первый strncat может вызывать неопределенное поведение, так как в выделенной памяти не должно быть 0 байт.

Выделите буфер до line перед входом в цикл и напишите 0 в первый байт.

Кроме того, не используйте

line = realloc(line, ++count * BUFFER_SIZE * sizeof(char)); 

Если realloc не удается, утечка памяти. И вы должны проверить возвращаемое значение realloc,

char *temp = realloc(line, ++count * BUFFER_SIZE * sizeof(char)); 
if (temp == NULL) { 
    // Oops 
} else { 
    line = temp; 
} 

И не malloc к chunk в fgets вызова

while (fgets((chunk = malloc(sizeof(char) * BUFFER_SIZE)), BUFFER_SIZE, fp) != NULL) 

Если malloc выходит из строя, что вызывает неопределенное поведение тоже.malloc и проверить перед вызовом fgets,

while ((chunk = malloc(sizeof(char) * BUFFER_SIZE)) && fgets(chunk, BUFFER_SIZE, fp) != NULL) 
+0

отличный ответ, спасибо. В идиоме 'realloc' было бы уместно' free (line) 'if' temp == NULL'? Мне очень нравится, что при пересмотре условия тоже –

+0

Да, тогда 'line' все еще указывает на выделенную память, которая должно быть 'free'd. Какая диагностика и восстановление/очистка до выхода вы делаете, когда' realloc' терпит неудачу, зависит. –

1

Вы можете придерживаться realloc и MemSet ваших буферов до нуля раньше, хотя.

1

Ваша проблема здесь:

line = realloc(line, ++count * BUFFER_SIZE * sizeof(char));  

Согласно справочной странице для realloc:

"realloc(3) does not guarantee that the additional memory is also 
zero-filled." 

и

"If ptr is NULL, realloc() is identical to a call to malloc() for size bytes." 

Таким образом, любая новая память вы получите, скорее всего, будет заполнено с ненулевыми байтами, то есть в первый раз, когда он вызывается, у него, вероятно, не будет 0 f или первый байт, что означает, что strncat будет добавляться к тому, что байт-байт находится в распределении.