【问题标题】:Dynamically storing information from a file using C使用 C 动态存储文件中的信息
【发布时间】:2011-10-26 00:38:16
【问题描述】:

我是 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#”表示)。谁能帮助我更好地理解这一点?

【问题讨论】:

    标签: c pointers char realloc memset


    【解决方案1】:

    strcpy的实现很简单

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

    因此,src 指向的内存应以至少一个 '\0' 字符结尾。在您的情况下,单元素数组包含一个不为空的字符。因此,strcpy 超出了它的内存并最终在其段之外取消引用,从而导致错误。当像 strcpy(buff, "abcd") 这样调用时不会出现这种情况,因为编译器会将 abcd\0 放在程序的代码部分中。

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

    【讨论】:

      【解决方案2】:
      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);
      

      这不是您认为的意思。 (&amp;my_char)[1] 必须为零才能使其工作,因此这是未定义的行为。你应该试试这个:

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

      另外,您只分配了 1 个char。您需要 2 个才能正常工作。

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

      sizeof 不会神奇地记住你上次分配了多少,它只需要它指定的类型的编译时大小,在这种情况下相当于sizeof(char*) 在 32 或 64 上为 4 或 8-位系统分别。您需要跟踪变量中的实际分配大小。还有这种realloc在失败时容易泄漏内存,你应该这样做:

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

      继续……

             strcat(token, &my_char);
      

      这与上次使用 &amp;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);
      

      【讨论】:

        【解决方案3】:
        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。该值是从文件中读取的下一个字符(表示为无符号字符,然后转换为 int,因此通常在 0..255 范围内)EOF(通常是-1)。如果在您的系统上签署了普通字符,则恰好是 255 的输入字符将导致您的循环过早终止;如果普通 char 是无符号的,您的循环可能永远不会结束,因为您将 EOF 的负值转换为有符号值。我实际上不是 100% 确定在后一种情况下会发生什么,但这没关系;将my_char 设为整数。

        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() 的第二个参数必须是指向字符串的指针。 &amp;my_char 是正确的类型,但如果内存中 my_char 后面的字节不是 '\0', Bad Things Can Happen

        这不是一篇详尽的评论。

        推荐阅读:comp.lang.c FAQ

        【讨论】:

        • 我可以确认我已经看到了带有类似while((my_char = (char)fgetc(infile)) != EOF){的代码的无限循环
        • 第二段我就知道是你!很好的细分,“其他基思”。 :)
        • 我没有遇到无限循环的问题。也许这与我使用 nvcc 而不是 gcc 的事实有关?
        • @Erik:除非普通字符是无符号的,否则它不会导致无限循环。但是不要浪费太多时间来担心分配fgetc' to a char` 的结果恰好起作用的情况。只需修复代码以使用 int(并删除不必要的演员表)。
        • @Keith:很简单。顺便说一句,帖子中的链接非常有帮助。对于指针和数组的进一步阅读,您还有其他建议吗?该链接很有帮助,但我仍然不清楚。
        【解决方案4】:

        主要问题似乎是空终止字符串的问题。 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 调用中删除&amp;)。编译器警告/错误将有助于解决这些变化。

        【讨论】:

          【解决方案5】:

          您的my_char 应该是int,因为这是fgetc 返回的内容,使用char 将意味着您永远找不到您的EOF 条件:

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

          EOF 值是int,它不是有效的char,这就是您可以在一次读取一个字节并从fine manual 读取文件时检测文件结尾的方法:

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

          其他人已经指出了你的记忆错误,所以我将不理会这些。

          【讨论】:

          • 实际上,铸造工作正常。我不确定编译器版本是否会影响这一点。顺便说一句,我正在使用 nvcc。
          • @Erik:但 EOF 是一个 int,它不适合 char,因此您可以区分文件结尾和有效字节。如果它有效,那么您的输入就很幸运了。
          【解决方案6】:

          首先,与一次读取 1 个字符相比,一次读取 1 个整行会容易得多。然后您可以使用strtok() 用逗号分隔行。

          您的代码存在一些问题:

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

          这只会分配 1 个字节。 C 字符串必须以 null 结尾,因此即使是长度为 1 的字符串也需要 2 个字节的分配空间。

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

          my_char 是单个字符,而不是以 null 结尾的字符串(这是 strcpy()strcat() 所期望的)。

          sizeof(token)
          

          这不是你的意思。这将返回一个指针的大小(它是token 的类型。您可能想要strlen() 之类的东西,但您必须重构代码以确保您使用的是空终止字符串,而不是到单个字符。

          【讨论】:

          • 啊,有道理。我习惯了 Java 的 StringTokenizer 效率低下。 strtok() 会像 Java 的 StringTokenizer 那样制作不必要的副本吗?
          • strtok() 不会制作任何额外的副本,但它会将NULLs 粘贴在您给它的字符串中分隔符出现的位置,因此请确保您没有破坏字符串的副本如果您打算再次使用它。此外,strtok() 不是线程安全的; strtok_r() 是一个可重入的版本。
          【解决方案7】:

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

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

          但是,由于您只清零一个字节,因此您的字符串不一定以空值结尾。您最有可能看到的是 char * 之后内存中的额外垃圾。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多