动态内存分配和输入函数scanf

7

我在一些脚本语言方面还算比较熟练,但现在我想学习原始的C语言。目前我只是在玩一些基础内容(目前在进行I/O操作)。如何分配堆内存,将字符串存储在分配的内存中,然后再将其输出?这是我目前所拥有的,如何使其正常工作?

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

int main(int argc, char *argv[])
{
  char *toParseStr = (char*)malloc(10);
  scanf("Enter a string",&toParseStr);
  printf("%s",toParseStr);
  return 0;
}

目前我收到了奇怪的输出,例如'8'\'。


在ISO C中,使用<stdlib.h>头文件包含时,您不需要将malloc(3)的返回类型强制转换。 - sarnold
值得指出的是,在这里你应该使用堆栈。 - dicroce
5个回答

9

你需要给scanf提供一个转换格式,这样它就知道你想读取一个字符串了——现在,你只是展示了你分配的内存中存在的任何垃圾。不要试图描述所有问题,下面是一些代码,应该至少接近工作:

char *toParseStr = malloc(10);
printf("Enter a string: ");
scanf("%9s", toParseStr);
printf("\n%s\n", toParsestr);
/* Edit, added: */ 
free(toParseStr);
return 0;

编辑:在这种情况下,free字符串并没有任何实际的区别,但正如其他人指出的那样,养成这个好习惯仍然是很重要的。


1
与某些人的看法相反,刷新stdout并不是确保提示出现在输入被读取之前必要的,除非实现确实存在问题。对于那些真正关心的人,请参见§7.19.3。只有当可以确定stdout不是交互设备时,它才能完全缓冲。 - Jerry Coffin
1
你错了。stdout 仍然可以是行缓冲,这意味着在打印换行符之前不会显示任何内容。POSIX 建议在读取时刷新 stdout 和其他这样的行缓冲流,但是扫描开放文件列表以寻找行缓冲流(特别是使用线程和锁定)将对性能产生重大影响,并且实现可能由于非常好的原因选择不这样做。据我所知,ISO C 对缓冲语义几乎没有要求。因此,你 应该 刷新! - R.. GitHub STOP HELPING ICE

9
  char *toParseStr = (char*)malloc(10);
  printf("Enter string here: ");
  scanf("%s",toParseStr);
  printf("%s",toParseStr);
  free(toParseStr);

首先,scanf 中的字符串指定了它要接收的输入。为了在接受键盘输入之前显示一个字符串,请使用 printf 如下所示。
其次,你不需要解引用 toParseStr,因为它指向大小为 10 的字符数组,而你已经使用 malloc 分配了内存。只有当你使用一个函数将其指向另一个内存位置时,才需要使用 &toParseStr
例如,假设你想编写一个分配内存的函数,那么你就需要 &toParseStr,因为你正在更改指针变量的内容(它是内存中的一个地址 --- 你可以通过打印它的内容来查看)。
void AllocateString(char ** ptr_string, const int n)
{
    *ptr_string = (char*)malloc(sizeof(char) * n);
}

如您所见,它接受 char ** ptr_string,它的意思是指向存储指针内存位置的指针,该指针将会存储分配了n个字节的内存块的第一个字节的内存地址(现在由于未初始化,它具有一些垃圾内存地址)。

int main(int argc, char *argv[])
{
  char *toParseStr;
  const int n = 10;
  printf("Garbage: %p\n",toParseStr);
  AllocateString(&toParseStr,n);
  printf("Address of the first element of a contiguous array of %d bytes: %p\n",n,toParseStr);

  printf("Enter string here: ");
  scanf("%s",toParseStr);
  printf("%s\n",toParseStr);
  free(toParseStr);

  return 0;
}

第三,建议您释放您分配的内存。即使这是您的整个程序,在程序退出时此内存将被释放,但这仍然是一个良好的实践。


3
即使是小程序也应该赞成自由。这让我想起了“聚沙成塔”的谚语。;-) - Praveen S
在调用 scanf 之前,应该在打印提示信息后调用 fflush(stdout);。大多数实现会为您做这件礼貌的事情,但这并不是强制性的。 - R.. GitHub STOP HELPING ICE

5
使用标准的 "%s" 格式符,使用 scanf()(或者在处理不可控数据时使用 fscanf())几乎是导致缓冲区溢出的必然方式。
经典例子是,如果我在您的程序中输入字符串 "This string is way more than 10 characters",那么就会出现混乱,猫狗会开始睡在一起,裸奔奇点可能会吞噬地球(大多数人只说“未定义行为”,但我认为我的描述更好)。
我积极反对使用不能提供保护的函数。我强烈建议您(特别是作为 C 新手)使用 fgets() 读取输入,因为它可以更轻松地控制缓冲区溢出,并且更适合于简单的行输入,而不是 scanf()
一旦您获取了一行,然后您可以随心所欲地调用 sscanf(),顺便说一句,在这种情况下您根本不需要这样做,因为您只是获取一个原始字符串。
我会使用:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUFFSZ 10

int main(int argc, char *argv[]) {
  char *toParseStr = malloc(BUFFSZ+2);
  if (toParseStr == NULL) {
      printf ("Could not allocate memory!\n");
      return 1;
  }
  printf ("Enter a string: ");
  if (fgets (toParseStr, BUFFSZ+2, stdin) == NULL) {
      printf ("\nGot end of file!\n");
      return 1;
  }
  printf("Your string was: %s",toParseStr);
  if (toParseStr[strlen (toParseStr) - 1] != '\n') {
      printf ("\nIn addition, your string was too long!\n");
  }
  free (toParseStr);
  return 0;
}

1
+1,不过我想补充一点,虽然 fgets 有优势,但是 scanffscanf 也有防止缓冲区溢出的措施。 - Jerry Coffin
这是个好观点,@Jerry。虽然我很少看到人们在“%s”中使用宽度说明符 :-) 由于我的大多数控制台I/O代码倾向于基于行的输入,因此%s不适合获取空格。但是,在这种情况下,你的答案实际上是正确的,所以给你加1分。 - paxdiablo
2
另一个有趣的可能性是 scanf("%9[^\n]", your_string); -- 这是从 scanf 中以行为基础的字符串输入,不管它有多少值得关注。 - Jerry Coffin
@Jerry Coffin:scanffscanf通常也很难使用,对于其他原因也是如此。在我看来,除非是C专家,否则最好完全避免使用它们。无论如何,对于唯一一个提醒潜在缓冲区溢出的答案给予加分。 - jamesdlin
1
@Jerry:对于好的%[建议点赞。很少有人知道它的存在。实际上,它非常有用,可以在普通的ISO C上实现完全可移植的GNU getline/getdelim版本。如果在其后使用%n,甚至可以获取读取的字节数,在数据读取中包含嵌入的空字节的情况下。 - R.. GitHub STOP HELPING ICE

3

scanf中,您不需要在toParseStr之前加上&,因为它已经是一个指针。

同时,在使用完后,请调用free(toParseStr)


1
根据bball的系统,可能需要在printf中加入“\n”才能正确显示内容。此外,10个字符是一个非常短的字符串。 - George
1
虽然这是真的,但这并不是一个问题的源头(在这种情况下,“&”是不必要的,但无害的)。 - Jerry Coffin
2
@Jerry 这是无害的,因为格式说明符没有指定任何参数,但一旦他修复了它并像你的答案一样加上 %s,它就会导致段错误。 - Michael Mrozek
@Michael:是的,但它只指向一个相对较小的问题,而有许多其他问题更为严重。特别是,在不改变代码的其余部分的情况下更改该特定点将不会在行为上提供任何(可见的)改进。 - Jerry Coffin

0
首先,导致您的程序无法工作的错误是:scanf(3)需要一个格式字符串,就像printf(3)一样,而不是要为用户打印的字符串。其次,您传递的是指针toParseStr的地址,而不是指针toParseStr本身。

我还从您对malloc(3)的调用中删除了不必要的强制类型转换。

您的程序仍然需要改进的一点是使用scanf(3)a选项来为您分配内存,以便某些恶意用户将十个字符放入您的字符串中时不会开始践踏与之无关的内存。(是的,C语言会让某些人通过这个程序覆盖几乎整个地址空间,这是一个巨大的安全漏洞。:)

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

int main(int argc, char *argv[])
{
  char *toParseStr = malloc(10);
  printf("Enter a short string: ");
  scanf("%s",toParseStr);
  printf("%s\n",toParseStr);
  return 0;
}

1
scanf 没有 a 选项。这是 GNU 扩展,不仅非标准,而且与 ISO C 发生了冲突%a 是用于读取浮点数的格式说明符之一!)。应绝对避免使用。 - R.. GitHub STOP HELPING ICE
谢谢您;我不知道这个扩展与ISO C冲突。 - sarnold

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