1

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

4

7 回答 7

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

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

不要投射malloc(). 这不是必需的(malloc()返回 avoid*所以可以隐式转换),它可以隐藏错误。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 常见问题解答

于 2011-08-12T17:50:40.103 回答
0

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

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

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

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

于 2011-08-12T17:52:29.963 回答
0

的实现strcpy很简单

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

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

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

于 2011-08-13T02:20:37.077 回答
0

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

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

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

于 2011-08-12T17:53:50.257 回答
0

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

您的代码存在一些问题:

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

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

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

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

sizeof(token)

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

于 2011-08-12T17:57:36.763 回答
0

my_char应该是int因为那是fgetc返回的,使用 achar将意味着你永远找不到你的 EOF 条件:

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

EOF值是int无效的char,这就是您可以在一次读取一个字节并从精美手册中检测文件结尾的方式:

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

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

于 2011-08-12T17:58:54.987 回答
0
while((my_char = (char)fgetc(infile)) != EOF){

这是糟糕的时期。 fgetc返回int。它可以表示比 更多的值charEOF通常是-1. 由于您存储在 achar中,您希望如何表示字符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*)32 或 64 位系统上的 4 或 8。您需要跟踪变量中的实际分配大小。这种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);
于 2011-08-12T18:02:32.410 回答