<commit_before>---
title: Standard I/O Learning (2)
---

> Under  normal circumstances every UNIX program has three streams opened for it when it starts up, one for input, one for output,  and one for printing diagnostic or error messages.

### Scanf 访问越界

先来看看上一篇中那个程序代码和执行输出:

Code:

	char s[4];
	scanf ("%s", s); 
	fprintf (stdout, "%s\n", s); 
	
	fgetc (stdin);
	
	char buf[5];
	fgets (buf, 5, stdin);
	fprintf (stdout, "%snew line", buf);

output:

	scanf
	scanf
	fgets
	fgetnew line

代码中，我们定义了一个 4 个字节长度的 char 数组 s[4]，然后执行过程中输入了 5 个字符， “scanf“。按通常情况，我们最多
允许输入 3 个字符，最后一个字符作为 '\0'。但从输出来看，这里 s 的打印输出完全正确。 虽然正确，但这里已经出现访问越界
了，由于 scanf 并不检查访问越界，所以这种情况下超出数组长度的字符会去占用其他的内存地址，我们来修改一下代码，看看 gdb 的一些调试结果。

修改后的代码：

	struct string
	{	
		char s[5];
		char s2[4];
	};

	struct string str;
	scanf ("%s", str.s);
	fprintf (stdout, "%s\n", str.s);

gdb 调试：

	Temporary breakpoint 1, main () at a.c:26
	26		scanf ("%s", str.s);
	(gdb) display &str.s[4]
	1: &str.s[4] = 0x7fffffffdf44 "\377\177"
	(gdb) display &str.s2
	2: &str.s2 = (char (*)[4]) 0x7fffffffdf45
	(gdb) display &str.s[5]
	3: &str.s[5] = 0x7fffffffdf45 "\177"
	(gdb) n
	scanfffo
	27		fprintf (stdout, "%s\n", str.s);
	3: &str.s[5] = 0x7fffffffdf45 "ffo"
	2: &str.s2 = (char (*)[4]) 0x7fffffffdf45
	1: &str.s[4] = 0x7fffffffdf44 "fffo"
	(gdb) display str.s2
	4: str.s2 = "ffo"
<commit_msg>Update 2015-6-5-Standard-IO-two.md<commit_after>---
title: Standard I/O Learning (2)
---

> Under  normal circumstances every UNIX program has three streams opened for it when it starts up, one for input, one for output,  and one for printing diagnostic or error messages.

### Scanf 访问越界

先来看看上一篇中那个程序代码和执行输出:

Code:

	char s[4];
	scanf ("%s", s); 
	fprintf (stdout, "%s\n", s); 
	
	fgetc (stdin);
	
	char buf[5];
	fgets (buf, 5, stdin);
	fprintf (stdout, "%snew line", buf);

output:

	scanf
	scanf
	fgets
	fgetnew line

代码中，我们定义了一个 4 个字节长度的 char 数组 s[4]，然后执行过程中输入了 5 个字符， “scanf“。按通常情况，我们最多
允许输入 3 个字符，最后一个字符作为 '\0'。但从输出来看，这里 s 的打印输出完全正确。 虽然正确，但这里已经出现访问越界
了，由于 scanf 并不检查访问越界，所以这种情况下超出数组长度的字符会去占用其他的内存地址，我们来修改一下代码，看看 gdb 的一些调试结果。

修改后的代码：

	struct string
	{	
		char s[5];
		char s2[4];
	};

	struct string str;
	scanf ("%s", str.s);
	fprintf (stdout, "%s\n", str.s);

gdb 调试：

	Temporary breakpoint 1, main () at a.c:26
	26		scanf ("%s", str.s);
	(gdb) display &str.s[4]
	1: &str.s[4] = 0x7fffffffdf44 "\377\177"
	(gdb) display &str.s2
	2: &str.s2 = (char (*)[4]) 0x7fffffffdf45
	(gdb) display &str.s[5]
	3: &str.s[5] = 0x7fffffffdf45 "\177"
	(gdb) n
	scanfffo
	27		fprintf (stdout, "%s\n", str.s);
	3: &str.s[5] = 0x7fffffffdf45 "ffo"
	2: &str.s2 = (char (*)[4]) 0x7fffffffdf45
	1: &str.s[4] = 0x7fffffffdf44 "fffo"
	(gdb) display str.s2
	4: str.s2 = "ffo"

代码修改定义了一个结构体，用来存储两个字符数组。 由前面的文章 Issues of Alignment 知道，这样两个数组在内存空间上的地址
就连续了，这个通过 gdb 的调试结果也能看出。 s[4] 的下一个地址就是 s2 的首地址。但这里也有个地方值得注意，就是 s.[5] 的地址和 s2 的首地址一样，但明显 s[5] 是未定义的，这里访问已经越界了，因此看来 C 在一般情况是不检查数组越界的（可以
通过函数来完成）。

{'fraction_non_alphanumeric': 0.15975317106616388, 'fraction_numerical': 0.053136784367500856, 'mean_word_length': 4.065972222222222, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 12, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3378324', 'n_tokens_mistral': 1734, 'n_tokens_neox': 1760, 'n_words': 357}