我是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#'表示)。谁能帮助我更好地理解这一点?
答案 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
之后的字节是来自堆栈的“随机”值。
您需要分配比字符串长度长一个字节(并重新分配一个字节长)以允许空终止符。并且strcpy
和strcat
调用对源“字符串”无效,而字符串实际上只是一个字符。要继续使用您正在实现的基本逻辑,只需将字符值分配给token
数组中的适当位置即可。或者,您可以将my_char
声明为双字节字符数组,并将第二个字节设置为0终止符以允许使用strcpy
和strcat
。例如,
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
放在程序的代码部分中。
要解决一般问题,使用fgetline
和strtok
将是解决问题的更好,更简单的方法。