2010-08-30 33 views
8

首先(一如既往)我想道歉我的英语,它可能不够清楚。阅读字符串C中未定义的长度

我不擅长C编程,我被要求读取一个长度未定义的“字符串”输入。

这是我的解决方案

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

char *newChar(); 
char *addChar(char *, char); 
char *readLine(void); 

int main() { 
    char *palabra; 
    palabra = newChar(); 

    palabra = readLine(); 
    printf("palabra=%s\n", palabra); 

    return 0; 
} 

char *newChar() { 
    char *list = (char *) malloc(0 * sizeof (char)); 
    *list = '\0'; 
    return list; 
} 

char *addChar(char *lst, char num) { 
    int largo = strlen(lst) + 1; 
    realloc(&lst, largo * sizeof (char)); 
    *(lst + (largo - 1)) = num; 
    *(lst + largo) = '\0'; 
    return lst; 
} 

char *readLine() { 
    char c; 
    char *palabra = newChar(); 

    c = getchar(); 
    while (c != '\n') { 
    if (c != '\n') { 
     palabra = addChar(palabra, c); 
    } 
    c = getchar(); 
    } 
    return palabra; 
} 

请,我会很感激你帮助我,告诉我,如果这是一个好主意,或者给我一些其他的想法(也告诉我,如果这是一个“正确的”用于指针)。

在此先感谢


编辑:嗯,谢谢你的回答,他们是非常有用的。现在我发布了编辑(我希望更好)的代码,也许可以对新来C的人有用(像我一样)并再次被反馈。

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 


void reChar(char **, int *); 
void readLine(char **, int *); 

int main() { 
    char *palabra = NULL; 
    int largo = 0; 

    reChar(&palabra, &largo); 
    readLine(&palabra, &largo); 
    printf("palabra=%s\n", palabra, largo); 

    system("pause"); 
    return 0; 
} 

void reChar(char **lst, int *largo) { 
    (*largo) += 4; 
    char *temp = (char*) realloc(*lst, (*largo) * sizeof (char)); 

    if (temp != NULL) { 
     *lst = temp; 
    } else { 
     free(*lst); 
     puts("error (re)allocating memory"); 
     exit(1); 
    } 
} 

void readLine(char **lst, int *largo) { 
    int c; 
    int pos = 0; 

    c = getchar(); 
    while (c != '\n' && c != EOF) { 
     if ((pos + 1) % 4 == 0) { 
      reChar(lst, largo); 
     } 
     (*lst)[pos] =(char) c; 
     pos++; 
     c = getchar(); 
    } 
    (*lst)[pos] = '\0'; 
} 

PS:

  • 这似乎够慢的 “言论报” 增加大小。

  • 我不知道如果捕获getchar()int,然后将它转换成一个char是正确的方式来hadle EOF pitfall

+0

'sizeof(char)'保证始终为1.与您在答案中找到的有效备注相比,这是微不足道的。 – 2010-08-30 07:49:45

+0

'int main()'不是main的有效签名。使用'int main(void)'或'int main(int argc,char * argv [])''。 – mk12 2012-08-26 01:55:10

回答

23
  1. 查找POSIX getline()的定义。

  2. 请记住,您需要从realloc()捕获返回值;不保证新的内存块与旧的内存块位置相同。

  3. 知道malloc(0)可能会返回一个空指针,或者它可能会返回一个不可用的非空指针(因为它指向零字节的内存)。

  4. 当列表指向分配的内存的零字节时,不能写'*list = '\0';;你没有写入权限。如果你得到一个NULL,你可能会得到一个核心转储。在任何情况下,您都会调用未定义的行为,即'坏主意™'。 (Thanks

  5. palabra = newChar(); in main()泄漏内存 - 假设您修复了已讨论过的其他问题。

  6. readLine()中的代码不考虑在获得换行符之前获取EOF的可能性;这是不好的,当内存分配(最终)失败时将导致核心转储。

  7. 由于每次分配一个字符,因此您的代码将显示较差的性能。通常,你应该一次分配多于一个额外的角色;从大约4个字节的初始分配开始,每次需要更多空间时将分配加倍可能会更好。保持初始分配较小,以便重新分配代码得到正确测试。

  8. getchar()返回的值是int而不是char。在大多数计算机上,它可以返回256个不同的正字符值(即使char是带符号的类型)和单独的值EOF,它与所有值不同。 (该标准允许它返回多于256个不同的字符,如果机器具有分别大于8位字节)(Thanks)C99标准§7.19.7.1说的fgetc()

    如果最终用于流指向的输入流的文件指示符未被设置并且存在下一个字符,则fgetc函数获得该字符作为转换为int的无符号的 字符,并且前进 的关联文件位置指示符流(如果定义)。

    (着重。)它定义getchar()getc()方面,并且它在fgetc()术语定义getc()

  9. (借:Thanks)。 realloc()的第一个参数是指向当前分配内存开始的指针,而不是指向当前分配内存开始指针的指针。如果你没有从它那里得到编译警告,那么你没有在你的编译器上编译足够的警告。你应该把警告提到最大。你应该注意编译器的警告 - 它们通常代表代码中的错误,特别是在你还在学习语言的时候。

  10. 在没有空终止符的情况下保留字符串通常比较容易,直到知道您已经到达行尾(或输入结束)为止。当没有更多的字符被读取(暂时)时,然后追加空字符,以便字符串在返回之前被正确终止。只要您跟踪字符串中的位置,这些函数就不需要在读取字符串时正确终止字符串。尽管如此,确保在任何时候都有足够的空间将NUL 添加到字符串的末尾。

见Kernighan的&派克'The Practice of Programming'了很多相关的讨论。我也认为Maguire 'Writing Solid Code'提供了相关的建议,尽管它有些过时。但是,您应该意识到,有些人会对此书进行排斥。因此,我建议使用TPOP而不是WSC(但亚马逊的WSC售价为0.01美元+ p & p,而TPOP的售价为20.00美元+ p & p - 这可能是市场说的话)。


TPOP之前在 http://plan9.bell-labs.com/cm/cs/tpophttp://cm.bell-labs.com/cm/cs/tpop但都是现在(2015年8月10日)打破。 另请参阅维基百科关于TPOP

+0

+ 1为执行提示,我正要写相同的 – 2010-08-30 06:59:20

+1

+1,但在8小调:它可以返回*至少* 256不同的*无符号* char值... – schot 2010-08-30 08:07:17

+0

+1,但“如果你得到一个NULL,你会得到一个核心转储“ - 写入NULL与写入任何其他无效指针一样没有定义。即使在POSIX系统上,也不保证核心转储(在典型的POSIX程序中,不可能将任何东西映射为0,但它确实发生......) – bdonlan 2010-08-30 10:21:39

5
  • 你总是分配比你一个字节少使用。例如,在开始时为空字符分配零个字符,然后尝试将(不存在的)第一个字符设置为'\0'

  • realloc不带指针指针作为第一个参数。它应该像这样使用:

    lst = realloc(lst, largo * sizeof (char)); 
    
  • 如果要处理,你就必须检查是否malloc()realloc()返回NULL内存不足的条件。

  • 在开始时分配一个更大的缓冲区并以更大的步幅增加它会更有效,而不是分别重新分配每个添加的字符。

+1

如果'realloc()'失败,那么你已经泄漏了你以前的内存。不要将'realloc()'的结果赋给第一个参数! – 2010-08-30 07:15:29

+0

'* sizeof(char)'是多余的。 – mk12 2012-08-19 00:45:17

2

的第一个参数调用realloc

realloc(&lst, largo * sizeof (char)); 

lst而不是&lst

此外,通过realloc返回的指针不必总是相同的第一个参数。如果找不到与现有内存相邻的空闲内存,则会分配一个完全不同的块并返回其地址。

char *new_lst = realloc(lst, largo * sizeof (char)); 
if(new_lst != NULL) { 
    lst = new_lst; 
} 
1

除了在你的代码中的错误,我认为这是更好一旦你有,你可以写一个函数getline()函数来创建C中的可变长度的字符串。这个可变长度的字符串包含了容量的概念,所以它的大小以2的幂数增加,而不是一个一个地增加。

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

typedef struct _mystring { 
    char * native; 
    size_t size; 
    size_t capacity; 
} String; 

size_t String__len(String this) 
{ 
    return this.size; 
} 

String String__create(char native[], size_t capacity) { 
    String this; 

    this.size = strlen(native); 
    if (capacity < (this.size + 1)) 
     this.capacity = this.size + 1; 
    else this.capacity = capacity; 

    this.native = (char *) malloc(capacity * sizeof(char)); 
    strcpy(this.native, native); 

    return this; 
} 

String * String__set(String *this, char native[]) { 
    this->size = strlen(native); 

    if (this->size >= this->capacity) { 
     do { 
      this->capacity <<= 1; 
     } while(this->size > this->capacity); 

     this->native = realloc(this->native, this->capacity); 
    } 

    strcpy(this->native, native); 

    return this; 
} 

String * String__add(String *this, char ch) { 
    ++(this->size); 

    if (this->size >= this->capacity) { 
     do { 
      this->capacity <<= 1; 
     } while(this->size > this->capacity); 

     this->native = realloc(this->native, this->capacity); 
    } 

    char * zeroPos = this->native + (this->size -1); 
    *(zeroPos++) = ch; 
    *zeroPos = 0; 

    return this; 
} 

void String__delete(String *this) 
{ 
    free(this->native); 
} 

一旦这个实现完成的,这是这个问题和许多其他有用的,你可以创建getline函数:

String String__getLine() 
{ 
    int ch; 
    String this = String__create("", 16); 

    do { 
     ch = fgetc(stdin); 
     String__add(&this, ch); 
    } while(ch != EOF 
      && ch != '\n'); 

    size_t len = String__len(this); 
    this.size = len -1; 
    *(this.native + this.size) = 0; 

    return this; 
} 

现在你可以使用它:

int main() 
{ 
    printf("Enter string: "); 
    String str = String__getLine(); 
    printf("You entered: '%s'\n", str.native); 
    String__delete(&str); 

    return EXIT_SUCCESS; 
} 
+0

不要忘记说明如何使用String_delete()以避免内存泄漏。 – 2010-08-30 08:00:31

+0

另请注意,C中允许使用双下划线,但在C++中不允许。尽管这是一个C问题,而不是一个C++问题(C++中已经有很好的解决方案),但我认为编写无法轻易迁移到C++的代码毫无意义。 – 2010-08-30 08:02:54

+0

谢谢你的advie。 – Baltasarq 2010-08-30 08:40:14

1

这里是realloc和fgets的一个工作示例。它的C89,不需要POSIX。您可以使用您自己的预先分配的内存或NULL来设置参数。始终需要终止“免费”。

#include <string.h> 
#include <stdlib.h> 
#include <stdio.h> 

char *getstringStdin(char *s) 
{ 
    char buffer[9]; 
    s=realloc(s,1); 
    *s=0; 
    while(fgets(buffer,9,stdin)) 
    { 
    s=realloc(s,strlen(s)+1+strlen(buffer)); 
    strcat(s,buffer); 
    if(strchr(s,'\n')) 
    { 
     *strchr(s,'\n')=0; 
     break; 
    } 
    } 
    return s; 
} 

main() 
{ 
    char *s; 
    while(*(s=getstringStdin(0))) /* a single Enter breaks */ 
    { 
    puts(s); 
    free(s); 
    } 
    free(s); 
    puts("end"); 
    return 0; 
} 
+0

你有和@ sth的答案一样的错误 - 如果'realloc()'失败,你泄露了你以前的内存。不要将'realloc()'的结果赋给第一个参数! – 2010-08-30 13:46:43