getchar 的思考v0.2

getchar()的思考

风中纸页 posted @ 2008年7月03日 07:14 in 程序设计 , 3860 阅读
转贴请保留本文作者及文章地址

 

    通常我们学到的库函数getchar()会从标准输入得到一个字符,其返回值为int(read as an `unsigned char', and cast to `int').返回值为int的原因大致就是为了照顾特殊的EOF吧。

     但如果运行下面的程序:

 

#include <stdio.h>

int main(void)
{
        char a = 0;

        while ( (a = getchar()) && (a != EOF)) {
                putchar(a);
        }
    return 0;
}

 

Hello,World!
Hello,World!

 

会发现居然能够输出整个字符串。其实跟踪一下代码即可发现:

 

7:        while ( (a = getchar()) && (a != EOF)) {
0040D71C A1 44 4A 42 00       mov         eax,[__iob+4 (00424a44)]
0040D721 83 E8 01             sub         eax,1
0040D724 A3 44 4A 42 00       mov         [__iob+4 (00424a44)],eax
0040D729 83 3D 44 4A 42 00 00 cmp         dword ptr [__iob+4 (00424a44)],0
0040D730 7C 21                jl          main+53h (0040d753)
0040D732 8B 0D 40 4A 42 00    mov         ecx,dword ptr [__iob (00424a40)]
0040D738 0F BE 11             movsx       edx,byte ptr [ecx]
0040D73B 81 E2 FF 00 00 00    and         edx,0FFh
0040D741 89 55 F8             mov         dword ptr [ebp-8],edx
0040D744 A1 40 4A 42 00       mov         eax,[__iob (00424a40)]
0040D749 83 C0 01             add         eax,1
0040D74C A3 40 4A 42 00       mov         [__iob (00424a40)],eax
0040D751 EB 10                jmp         main+63h (0040d763)
0040D753 68 40 4A 42 00       push        offset __iob (00424a40)
0040D758 E8 F3 04 00 00       call        _filbuf (0040dc50)
0040D75D 83 C4 04             add         esp,4
0040D760 89 45 F8             mov         dword ptr [ebp-8],eax
0040D763 8A 4D F8             mov         cl,byte ptr [ebp-8]
0040D766 88 4D FC             mov         byte ptr [ebp-4],cl
0040D769 0F BE 55 FC          movsx       edx,byte ptr [ebp-4]
0040D76D 85 D2                test        edx,edx
0040D76F 74 64                je          main+0D5h (0040d7d5)
0040D771 0F BE 45 FC          movsx       eax,byte ptr [ebp-4]
0040D775 83 F8 FF             cmp         eax,0FFFFFFFFh
0040D778 74 5B                je          main+0D5h (0040d7d5)
 

 

在手册中还有说明:'getchar'  is a macro, defined in `stdio.h'.  You can use `getchar' to get the next single character from the  standard  input  stream.   As  a  side effect,  `getchar'  advances the standard input's current position indicator.这个就解释了为什么会把整个终端输入的所有内容都输出。

别急,这还远远没有完。

这个程序还有的问题是关于getchar()这个函数的使用(返回值)。

RETURNS
The next character (read as an `unsigned char', and cast to `int')

这意味着getchar的返回值会是int,但请注意在程序中变量a的类型是char。问题就处在这里了:对于char这种类型是signed还是unsigned是由编译器决定的。那么将int类型的值赋给char类型显然会有数据截断(或者回转),对于0x80以外的数据显然就出了问题。

另外还有的问题是:

while ( (a = getchar()) && (a != EOF))

我们的比较条件是a != EOF, 当char 类型和int类型比较的时候会发生char shift到int类型,那么这个也和编译器实现相关,当char shift到int时会有以下问题:

如果对于char类型,编译器默认为unsigned char类型,那么对于0x80以外(最高位为1)的数据,那么在shift到int类型的时候就会成为0x00 00 00 FX,这样对于a!= EOF这个条件就成为了永远成立的无用判断了。

嗯,让我们想一想还有什么地方还有错呢?

  • 无匹配
Avatar_small
说:
2008年7月04日 00:56

本来就是由char转换来的怎么会产生截断呢?
一般都是
while((a=getchar()!=EOF){
}
这样写吧。

Head_small
风中纸页 说:
2008年7月05日 08:50

1,对于平台类型sizeof(int) == 4, sizeof(char) == 1来说,getchar()的返回值为int,强制转换到char型,必定会有截断的问题。

2,while((a=getchar()!=EOF)和while ( (a = getchar()) && (a != EOF))基本上没有什么本质区别。

谢谢关注

Avatar_small
说:
2008年7月05日 20:36

本来就是从char转换到int的,在从int转换为char,怎么会发生截断呢。

Head_small
风中纸页 说:
2008年7月07日 06:01

海兄,多谢关注。

我看了一下glibc 2.5 的实现:

int
getchar ()
{
int result;
_IO_acquire_lock (_IO_stdin);
result = _IO_getc_unlocked (_IO_stdin);
_IO_release_lock (_IO_stdin);
return result;
}

可以看到result 原本为int类型。

关于
#define _IO_getc_unlocked(_fp) '
(_IO_BE ((_fp)->_IO_read_ptr >= (_fp)->_IO_read_end, 0) '
? __uflow (_fp) : *(unsigned char *) (_fp)->_IO_read_ptr++)

这部分我倒是没有怎么看懂。还请赐教。谢谢!

Avatar_small
说:
2008年7月07日 06:41

先把指针转换为unsigned char *然后取值,再转换为int值。
这个宏的意思是先从缓冲区取值,如果已经到了缓冲区结尾,再从文件继续读入。

Head_small
风中纸页 说:
2008年7月08日 08:14

“先把指针转换为unsigned char *然后取值”,此时取到的是什么值呢? 是unsiged char么? 那么EOF(-1)是如何获得的呢?

并且这个问题关键在于:有些编译器比如VC对于char的默认值为signed char,但有些编译器对于char的默认值为unsigned char,比如arm-elf-gcc。

对于上面的程序(出错程序)执行echo a | ./test.exe(VC)或者echo a | arm-elf-run.exe a.out (arm-elf-gcc)的结果是 VC的没有问题,arm-elf-gcc的结果会死循环。

原因就是unsigned char对于-1会认为是255,导致while无法退出。

因此使用int是比较保险的。

Avatar_small
说:
2008年7月09日 22:06

应该是用int才对。
对于返回的unsigned char转换为int时,会进行零扩展而不是符号扩展,所以11111111会在前面添加0补足到int的长度,对于signed char则会执行符号扩展。
对于你说的截断我一直理解错了,不好意思。我说的意思是本来就是char通过int再转换为char不会有信息的丢失。从int转换为char会有数据的截断是肯定的。
先把指针转换为unsigned char指针,取得的当然是unsigned char,不过在赋值给result时会进行扩展,EOF返回的直接就是_uflow的返回值,应该不会是从缓冲区中得到的。可以设想如果缓冲区中有数据的话,那么上次执行系统调用read的时候,返回值肯定不会是-1.只有读到文件结尾的时候才会返回-1.

Head_small
风中纸页 说:
2008年7月10日 03:47

谢谢! 如果EOF是_uflow的返回值就是-1的话,那么我也就理解了。

KVS Sample Paper 说:
2022年9月22日 02:21

KVS Sample Paper 2023 Pdf Download for Kendriya Vidyalaya Sangathan Class 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 & 12 Arts, KVS Sample Paper Science & Commerce Stream Practice Paper Suggestions with Past years old exam Solved Question Bank for all Regional Students of English Medium, Hindi Medium & Urdu Medium Studying in KVS Schools across the Country. All the Kendriya Vidyalaya Sangathan Board Students can download the Sample Paper Suggestions with Model Papers along with Previous Years old Exam Solved Question Bank for all Languages & Subjects of the Course.


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter