用C语言读取未定义长度的字符串

8

首先(一如既往地),我要为我的英语道歉,可能不够清晰。

我并不擅长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;
}

请问,你能否帮忙告诉我这是否是一个好主意,或者给我一些其他的想法(并告诉我指针的“正确”使用方法)。

提前感谢。


编辑:好的,感谢你们的回答,它们非常有用。现在我发布了修改后(我希望更好)的代码,也许对于像我这样的新手会有所帮助,并再次得到反馈。

#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';
}

注:

  • 似乎逐渐增加“palabra”的大小已经足够。

  • 我不确定将getchar()捕获到一个int,然后将其转换为char是否是正确处理EOF陷阱的方法。


sizeof(char)保证始终为1。与答案中可以找到的有效评论相比,这只是小问题。 - Pascal Cuoq
int main() 不是 main 函数的有效签名。请使用 int main(void) 或者 int main(int argc, char *argv[]) - mk12
5个回答

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

  2. 记得要捕获realloc()的返回值;不能保证新的内存块从与旧的内存块相同的位置开始。

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

  4. 当list指向分配的零字节内存时,你不能写'*list = '\0';';你没有写入权限。如果返回NULL,你很可能会得到核心转储。无论如何,你都会调用未定义的行为,这是“A Bad Idea™”(谢谢)。

  5. main()中的palabra = newChar();会泄漏内存——假设你已经修复了已经讨论过的其他问题。

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

  7. 你的代码性能不佳,因为它一次只分配一个字符。通常情况下,你应该分配比一个字符多得多的空间;从初始分配4个字节开始,并在需要更多空间时每次将分配加倍可能会更好。保持初始分配较小,以便正确测试重新分配代码。

  8. getchar()的返回值是int,而不是char。在大多数机器上,它可以返回256个不同的正字符值(即使char是有符号类型),以及一个单独的值EOF,该值与所有char值都不同。(如果机器的字节比每个8位字节大,则标准允许它返回超过256个不同的字符。)(谢谢)C99标准§7.19.7.1对fgetc()说:

    如果指向流的文件结束指示器没有设置并且存在下一个字符,则fgetc函数获取该字符作为无符号char转换为int并推进流的关联文件位置指示器(如果定义了)。

    (重点添加。)它用getc()来定义getchar(),并用fgetc()来定义getc()

  9. (借用:谢谢)。realloc()的第一个参数是当前分配的内存的开头指针,而不是指向当前分配的内存开头指针的指针。如果你没有得到编译警告,那么你的编译器没有设置足够的警告。你应该将警告调到最大。你应该注意编译器警告——它们通常表明代码中存在错误,特别是在你仍在学习语言时。

  10. 通常情况下,直到你知道已经到达行末(或输入末尾),才不加空终止符保留字符串。当没有更多字符可读取(暂时)时,然后附加空终止符,以便在返回之前正确终止字符串。这些函数在读取时不需要正确终止字符串,只要你跟踪字符串中的位置即可。但是确保你

    请参阅Kernighan&Pike的'编程实践',其中包含许多相关讨论。我认为Maguire的'编写可靠代码'也提供了相关建议,但它有些过时。然而,您应该知道有人痛斥这本书。因此,我推荐TPOP而不是WSC(但亚马逊售价0.01美元+邮费的WSC,而TPOP起价20.00美元+邮费--这可能是市场在说话)。


    TPOP曾经在http://plan9.bell-labs.com/cm/cs/tpophttp://cm.bell-labs.com/cm/cs/tpop上,但两个网址现在(2015-08-10)都无法访问。另请参阅维基百科关于TPOP的内容。


1
+1,但是关于8的小问题:它可以返回至少256个不同的无符号char值... - schot
+1,但是“如果你得到一个NULL返回值,你会得到一个核心转储” - 向NULL写入与向任何其他无效指针写入一样未定义。即使在POSIX系统上,也不能保证会出现核心转储(在典型的POSIX程序中,很少有任何东西被映射到0,但确实会发生...) - bdonlan

5
  • You are always allocating one byte less than you are using. For example in the beginning you allocate space for zero characters and then try to set the (non-existing) first character to '\0'.

  • realloc doesn't take a pointer to a pointer as first parameter. It's supposed to be used like this:

    lst = realloc(lst, largo * sizeof (char));
    
  • If you want to handle out-of-memory conditions you would have to check if malloc() or realloc() returned NULL.

  • It would be more efficient to allocate a bigger buffer in the beginning and increase it in bigger steps instead of reallocating every added character separately.


1
如果realloc()失败,你就会泄漏之前拥有的内存。不要将realloc()的结果分配给第一个参数! - Jonathan Leffler
* sizeof(char) 是多余的。 - mk12

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
除了代码中的错误,我认为在 C 中创建可变长度字符串更好。一旦你有了它,就可以编写一个 getLine() 函数。这个可变长度字符串包括容量的概念,因此它的大小会按照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;
}

不要忘记演示使用 String_delete() 以避免内存泄漏。 - Jonathan Leffler
另外,请注意,在C中允许使用双下划线,但在C++中不允许。虽然这是一个关于C而不是C++的问题(已经有很好的C++解决方案可用),但我认为写那些无法轻松迁移到C++的代码是没有意义的。 - Jonathan Leffler

1

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

#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;
}

你和 @sth 的回答里有相同的 bug - 如果 realloc() 失败,你之前分配的内存将会泄露。不要把 realloc() 的返回值赋给第一个参数! - Jonathan Leffler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接