使用C动态存储文件中的信息

时间:2011-08-12 17:43:00

标签: c pointers char realloc memset

我是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 ***

我不明白为什么当我期望为1时令牌的长度为'5'以及跟随5的奇怪的字符(由'a#'表示)。谁能帮助我更好地理解这一点?

7 个答案:

答案 0 :(得分:2)

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,而不是char。该值是从文件读取的下一个字符(表示为unsigned char,然后转换为int,通常在0..255范围内)EOF(其中通常为-1)。如果在您的系统上签署了普通字符,则恰好为255的输入字符将导致您的循环过早终止;如果普通字符未签名,则您的循环可能永远不会结束,因为您将EOF的负值转换为有符号值。我实际上并不是百分之百确定在后一种情况下会发生什么,但这并不重要;将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

答案 1 :(得分:0)

主要问题似乎是空终止字符串的问题。 malloc调用正在分配1个字节。但strcpy复制字节,直到它到达空终止符(零字节)。因此,结果没有很好地定义,因为my_char之后的字节是来自堆栈的“随机”值。

您需要分配比字符串长度长一个字节(并重新分配一个字节长)以允许空终止符。并且strcpystrcat调用对源“字符串”无效,而字符串实际上只是一个字符。要继续使用您正在实现的基本逻辑,只需将字符值分配给token数组中的适当位置即可。或者,您可以将my_char声明为双字节字符数组,并将第二个字节设置为0终止符以允许使用strcpystrcat。例如,

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

然后有必要相应地更改my_char的用法(将值赋给my_char[0],并删除strcpy / strcat调用中的&。编译器警告/错误将有助于解决这些变化。

答案 2 :(得分:0)

您只需在代码中为字符串分配1个字节的数据:

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

但是,因为您只将一个字节归零,所以您的字符串不一定是空终止的。你最有可能看到的是你的char *之后的内存中的额外垃圾。

答案 3 :(得分:0)

首先,你可以更容易地一次读取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()之类的东西,但是你必须重构代码以确保你使用null-终止字符串而不是单个字符。

答案 4 :(得分:0)

my_char int应为char,因为这是fgetc返回的内容,使用int my_char; /*...*/ while((my_char = fgetc(infile)) != EOF) { 将意味着您永远不会找到您的EOF条件:

EOF

int值是一个char,它不是有效的{{1}},这就是在一次读取一个字节并从{{1}}读取文件结尾的方法。 {3}}:

  

如果fgetc()返回的整数值存储到char类型的变量中,然后与整数常量EOF进行比较,则比较可能永远不会成功,因为扩展为整数时char类型的变量的符号扩展是实现定义的。

其他人已经指出了你的记忆错误,所以我会留下那些。

答案 5 :(得分:0)

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);

答案 6 :(得分:0)

strcpy的实施就像

一样简单
while(*dest++ = *src++);

因此,src指向的内存预计会以至少一个'\ 0'字符结束。在您的情况下,单个元素数组包含一个非null的字符。因此,strcpy超出了它的内存并最终在其段之外解除引用,从而导致错误。在进行strcpy(buff, "abcd")之类的调用时,没有观察到这种情况,因为编译器将abcd\0放在程序的代码部分中。

要解决一般问题,使用fgetlinestrtok将是解决问题的更好,更简单的方法。