Динамическое хранение информации из файла с помощью C - PullRequest
1 голос
/ 12 августа 2011

Я новичок в C и пытаюсь выучить несколько вещей. То, что я пытаюсь сделать, это прочитать в файле и сохранить информацию. Поскольку формат будет CSV, план состоит в том, чтобы прочитать каждый символ, определить, является ли он числом или запятой, и сохранить числа в связанном списке. У меня проблема с чтением чисел длиной более одного символа, как в следующем примере.

5,2,24,5

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

#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

struct list {
  float value;
  struct list * next;
  struct list * prev;
};

int main( int argc, char *argv[] ){
  FILE *infile;
  char *token = NULL;
  char  my_char;

  /* Open the file. */
  // The file name should be in argv[1]
  if((infile = fopen(argv[1], "r")) == NULL) {
    printf("Error Opening File.\n");
    printf("ERROR: %s\n", strerror(errno));
    exit(1);
  }

  while((my_char = (char)fgetc(infile)) != EOF){
    //Is my_char a number?
    if(isdigit(my_char)){
      if(token == NULL){
        token = (char *)malloc(sizeof(char));
        memset(token, '\0', 1);
        strcpy(token, &my_char);
        printf("length of token -> %d\n", strlen(token));
        printf("%c\n", *token);
      } else {
        token = (char *)realloc(token, sizeof(token) + 1);
        strcat(token, &my_char);
        printf("%s\n", token);
      }
    }
  }

  free(token);
  fclose(infile);
}

А вот и вывод:

[estest@THEcomputer KernelFunctions]$ nvcc linear_kernel.cu -o linear_kernel.exe
[estest@THEcomputer KernelFunctions]$ ./linear_kernel.exe iris.csv
length of token -> 5
5
5a#1a#
5a#1a#3a#
5a#1a#3a#5a#
5a#1a#3a#5a#1a#
5a#1a#3a#5a#1a#4a#
*** glibc detected *** ./linear_kernel.exe: realloc(): invalid next size: 0x0000000001236350 ***

Я не понимаю, почему длина токена равна '5', когда я ожидаю, что она равна 1, и странно выглядящих символов, следующих за 5 (представленных как 'a #'). Может кто-нибудь помочь мне понять это немного лучше?

Ответы [ 7 ]

2 голосов
/ 12 августа 2011
char *token = NULL;

token = (char *)realloc(token, sizeof(token) + 1);

token - указатель.sizeof не дает вам выделенный размер порции памяти, на которую он указывает;он дает вам размер самого объекта указателя.По всей видимости, указатели в вашей системе занимают 4 байта (это типично), поэтому вы всегда перераспределяете до 5 байтов.

Еще несколько предложений:

exit(1);

exit(EXIT_FAILURE) более переносимо.

char my_char;

while((my_char = (char)fgetc(infile)) != EOF){

fgetc возвращает целое число, а не символ.Значением является либо следующий символ, считанный из файла (представленный как беззнаковый символ и затем преобразованный в int, поэтому обычно он находится в диапазоне 0..255) или значение EOF (которое обычно -1).Если в вашей системе подписан обычный символ, входной символ 255 приведет к преждевременному завершению цикла;если обычный символ без знака, ваш цикл может никогда не закончиться, потому что вы конвертируете отрицательное значение EOF в значение со знаком.Я на самом деле не уверен на 100%, что происходит в последнем случае, но это не имеет значения;сделать my_char int.

token = (char *)malloc(sizeof(char));

Не разыгрывать результат malloc().В этом нет необходимости (malloc() возвращает void*, поэтому он может быть неявно преобразован) и может скрывать ошибки.sizeof(char) равно 1 по определению.Просто напишите:

token = malloc(1);

и всегда проверьте возвращаемое значение;malloc() возвращает NULL в случае сбоя.

memset(token, '\0', 1);

Проще: *token = '\0';

Выделение одного байта, затем realloc() за один дополнительный байт за раз, вероятно, будет ужаснонеэффективно.

strcat(token, &my_char);

Второй аргумент strcat() должен быть указателем на строку.&my_char относится к типу, но если байт, следующий за my_char в памяти, не является '\0', Bad Things Can Happen.

Это не исчерпывающий обзор.

Рекомендуемое чтение: comp.lang.c FAQ .

0 голосов
/ 13 августа 2011

Реализация strcpy так же проста, как

while(*dest++ = *src++);

Таким образом, память, указанная src, должна заканчиваться хотя бы одним символом '\ 0'. В вашем случае массив из одного элемента содержит символ, который не является нулевым. Следовательно, strcpy выходит за пределы своей памяти и приводит к разыменованию вне своего сегмента, что приводит к ошибке. Этого не наблюдается при выполнении вызова, подобного strcpy(buff, "abcd"), потому что компилятор помещает abcd\0 в раздел кода программы.

Для решения вашей проблемы в целом, использование fgetline и strtok будет лучшим и простым способом ее решения.

0 голосов
/ 12 августа 2011
while((my_char = (char)fgetc(infile)) != EOF){

Это плохие времена. fgetc возвращает int. Может представлять больше значений, чем char. EOF обычно -1. Так как вы храните в char, как вы ожидаете представить символ 0xff? Вы не будете; в конечном итоге вы будете воспринимать это как EOF. Вы должны сделать это:

int c;

while ((c=fgetc(infile)) != EOF)
{
   char my_char = c;

Далее ...

       token = (char *)malloc(sizeof(char));

Вам следует проверить возвращаемое значение malloc. Вам также следует подумать о том, чтобы выделить больше, чем вам нужно, иначе каждый вызов realloc может потенциально скопировать символы, которые вы видели до сих пор. Вы получите лучшую алгоритмическую сложность, скажем, сделав каждый размер выделения степенью 2. Кроме того, в отличие от C ++, в C вам не нужно приводить из void*.

       memset(token, '\0', 1);
       strcpy(token, &my_char);

Это не то, что вы думаете, это значит. (&my_char)[1] должно быть равно нулю, чтобы это работало, так что это неопределенное поведение. Вы должны попробовать это:

token[0] = my_char;
token[1] = 0;

Кроме того, вы выделили только 1 char. Вам нужно 2, чтобы это сработало.

       token = (char *)realloc(token, sizeof(token) + 1);

sizeof волшебным образом не запоминает, сколько вы выделили в прошлый раз, он принимает только размер времени компиляции указанного типа, в данном случае эквивалентный sizeof(char*), который будет 4 или 8 на 32 или 64- битовые системы соответственно. Вам необходимо отслеживать реальный размер размещения в переменной. Также этот тип realloc подвержен утечке памяти при сбое, вы должны сделать это:

 void *ptr = realloc(token, new_length);
 if (!ptr) { /* TODO: handle error */ }
 token = ptr;

Двигаемся дальше ...

       strcat(token, &my_char);

Это имеет то же неопределенное поведение, что и при последнем использовании &my_char, как если бы это была строка C. Кроме того, даже если это сработало, это расточительно, поскольку strcat должен пройти всю строку, чтобы найти конец.

Резюме моих предложений следующее:

int c;
size_t alloc_size = 0;
size_t current_len = 0;
char *token = NULL;
void *ptr;

while ((c = fgetc(infile)) != EOF)
{
   if (is_digit(c))
   {
      if (alloc_size < current_len + 2)
      {
         if (!alloc_size)
         {
            // Set some arbitrary start size...
            //
            alloc_size = 64;
         }
         else
         {
            alloc_size *= 2;
         }

         if (!token)
            ptr = malloc(alloc_size);
         else
            ptr = realloc(token, alloc_size);

         if (!ptr)
         {
            free(token);
            return -1;
         }
      }

      token[current_len++] = c;
      token[current_len] = 0;
   }
}

/* TODO: do something with token... */

free(token);
0 голосов
/ 12 августа 2011

Ваше my_char должно быть int, потому что это то, что fgetc возвращает, использование char будет означать, что вы никогда не найдете свое условие EOF:

int my_char;
/*...*/
while((my_char = fgetc(infile)) != EOF) {

Значение EOF - это int, которое не является действительным char, именно так вы можете определить конец файла, читая его по одному байту за раз, и из тонкого руководства :

Если целочисленное значение, возвращаемое функцией fgetc (), сохраняется в переменной типа char и затем сравнивается с целочисленной константой EOF, сравнение никогда не может быть успешным, поскольку расширение знака переменной типа char при расширении до целого числа равно реализации.

Другие указали на ошибки вашей памяти, поэтому я оставлю их в покое.

0 голосов
/ 12 августа 2011

С одной стороны, вам будет намного проще читать 1 целую строку за раз, а не 1 символ за раз. Затем вы можете использовать strtok(), чтобы разделить строку запятыми.

Есть несколько проблем с вашим кодом:

token = (char *)malloc(sizeof(char));

Это выделит только 1 байт. Строки C должны заканчиваться нулем, поэтому даже для строки длиной 1 требуется 2 байта выделенного пространства.

strcpy(token, &my_char);
strcat(token, &my_char);

my_char - это одиночный символ, а не строка с нулевым символом в конце (чего ожидают strcpy() и strcat()).

sizeof(token)

Это не то, что вы хотите сделать. Это вернет вам размер указателя (это тип token. Возможно, вы захотите что-то вроде strlen(), но вам придется реорганизовать ваш код, чтобы убедиться, что вы используете строки с нулевым символом в конце, а не до одиночных символов.

0 голосов
/ 12 августа 2011

Вы выделяете только 1 байт данных для вашей строки в вашем коде:

token = (char *)malloc(sizeof(char));
memset(token, '\0', 1);

Однако, поскольку вы обнуляете только один байт, ваша строка не обязательно заканчивается нулем.Скорее всего, вы видите дополнительный мусор, который был в памяти после вашего символа *.

0 голосов
/ 12 августа 2011

Основная проблема, похоже, заключается в том, что строки завершаются нулем.Вызов malloc выделяет 1 байт.Но strcpy копирует байты, пока не достигнет нулевого терминатора (нулевого байта).Таким образом, результаты не очень хорошо определены, поскольку байт после my_char является «случайным» значением из стека.

Вам необходимо выделить на один байт больше (и перераспределить на один байт больше, чем длина строки), чтобы учесть нулевой терминатор.И вызовы strcpy и strcat недопустимы для исходной "строки", которая на самом деле является просто символом.Чтобы продолжить использовать базовую логику, которую вы реализуете, необходимо просто присвоить символьное значение соответствующей позиции в массиве token.В качестве альтернативы, вы можете объявить my_char как двухбайтовый массив символов и установить второй байт в качестве разделителя 0, чтобы разрешить использование strcpy и strcat.Например,

char my_char[2];
my_char[1] = '\0';

И тогда необходимо будет соответственно изменить использование my_char (присвойте значение my_char[0] и удалите & в вызовах strcpy / strcat).Предупреждения / ошибки компилятора помогут устранить эти изменения.

...