[怎样正确输出指针类型数据]char类型的数据输出

  摘要:本文讨论C语言指针类型数据的输出。根据国家标准,指出国内C语言教材输出指针时普遍存在的错误,并说明了指针的正确的输出方法。   关键词:C语言教材;格式转换;printf()函数;指针
  
  How to print a pointer properly
  HUANG Ying
  (School of Computer and Software Engineering, Nanjing Institute of Industry Technology, Nanjing, Jiangsu, 210046)
  
  Abstract:We discussed the output issue of the pointer type data in C Language.According to the national standard , we pointed out errors of the pointer output commonly existing in the textbooks about the C language programming.And we elucidated the proper method of the point output.
  Key words:the textbooks of the C language;conversion specifacation;printf() function;pointer
  1 引言
  指针是C语言中的一种数据类型。国内许多C语言教材在讲解这种类型数据的输出时,都存在着若干错误。例如,[4]第248页:
  int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};
  printf("%d,%d\n",a,*a);
  这里,作者用了“%d”格式转换输出a和*a这两个指针类型表达式的值。
  实际上,这种做法是错误的。但是由于这种错误相对于代码语法错误来说不是那么直截了当而是比较隐晦,所以往往容易被视而不见,甚至被误以为是正确的写法。
  2 为什么用“%d”输出指针是错误的
  除了使用“%d”这种错误的格式输出指针类型数据,使用“%o”、 “%x”(或“%X”)及“%u”等几种错误转换格式的情况也很常见。例如,[4]第224页:
  printf("%o",p);
  作者认为这条语句的“作用是以八进制形式输出指针变量p的值”。
  由于这几种错误的性质类似,所以这里也一并讨论。
  首先,根据[1]、[2]、[3],“%d”这种格式只用于输出int类型的数据,输出的结果为十进制整数形式的字符序列――“[-]dd…d”,因此,在一定条件下将输出一个负的十进制整数。仅此一点就足以断定用“%d”格式输出指针是错误的。因为指针数据类型并不等同于int数据类型;指针数据类型的值表示地址,然而地址不可能是负值。
  既然地址不可能是负值,而“%o”、“%x”(或“%X”)、“%u”这几种格式的输出结果都不是负值,那么用这几种格式输出指针类型的值是否可以呢?同样不可以。
  根据[2]、[3],“%o”、“%x”(或“%X”)、“%u”这三种格式都只用于输出unsigned类型的数据。unsigned数据类型和指针数据类型是截然不同的数据类型。C语言并没有规定指针类型数据的内部表示应该和unsigned类型一致,甚至没有规定这两种类型数据在机器内部应该如何表示,而且这两种数据的尺寸也未必相同。事实上,C语言自C89开始,就要求编译器应提供“stddef.h”并在其中提供“ptrdiff_t”类型的定义。“ptrdiff_t”类型这种类型是两个指针做减法运算得到的结果的类型,这间接地说明了指针数据类型并不必然等同于整数类型的尺寸。因此使用“%o”、 “%x”(或“%X”)及“%u”输出指针毫无依据可言,因而是错误的用法。
  [3](§7.19.6.1,p280)为此特意指出,“If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.”。这表明使用“%d”、“%o”、“%x”(或“%X”)、“%u”输出指针数据是一种未定义行为(undefined behavior)。未定义行为本质上就是程序的一种错误。因为编译器此时有任意的处理方式,都不违背语言标准。从代码的角度来说,使用“%d”、“%o”、“%x”(或“%X”)、“%u”都是没有明确意义的代码,没有明确意义的代码当然是错误的代码。
  许多使用“%d”、“%o”、 “%x”(或“%X”)及“%u”这几种格式输出指针的人往往有一个误区,这个误区来自于经验,那就是使用这种格式输出指针并没有出现错误,因而他们认为可以使用这些格式输出指针。
  然而,这种想法是根本站不住脚的。仔细推敲一下就不难发现,这种推理的基础是基于使用个别编译器的经验而已。个别编译器当然不代表所有编译器。这个道理就如同在某个编译器上int类型的尺寸是2B,但绝不能说C语言的int数据类型的大小就是2B一样。
  因此,在个别编译器上,指针尺寸的大小和表示方法可能确实与某种整数类型相同,但这绝不能说明在所有的编译器上指针的大小和表示方法都和某种整数类型相同。
  如果考察的范围广些,不难发现,指针就其一般而言,和整数类型大小不同的例子很多。在这种情况下,[4]第248页中的程序就会产生错误。例如,在针对DOS操作系统的编译器MSC 6和TC在以大内存模式编译时,这段程序就会得到错误的行为;此外,在不少64位机器环境下的编译器中,以“%d”、“%o”、 “%x”(或“%X”)及“%u”这几种格式输出指针类型的值也显然会发生错误。原因就在于,错误地假设了整数类型与指针类型具有相同的表示和尺寸。
  3输出指针正确的转换说明
  由于由于在各种不同环境下,指针的尺寸未必和任何整数类型相同,因为实现可能支持多种尺寸的指针。所以无论是K&R的经典名著[1],还是国家标准C90[2],以至于目前最新的国际标准C99[3],都明确指出调用格式化函数(如printf()、fprintf()等)输出指针类型数据的值应该使用转换说明符p,此时,对应的“实参应为指向void的指针。该指针的值将以实现定义的方式转换为一系列可印刷字符”。具体的输出的结果显然和具体实现有关。
  虽然格式化输出函数只能输出void *类型的指针,但由于printf()函数的函数原型为:
  int printf ( const char* , ... ) ;
  C语言规定,与“...”部分相对应的指针类型的实参,在调用时都将被按照隐式类型转换的规则一律转换为“void *”类型的指针,因此,%p这种转换输出格式实际上同样适合于输出其他类型指针的值。
  由此,不难得出结论,调用printf()函数输出指针类型的值,应该使用%p格式转换声明。所以,[4]第248页的代码,正确的写法分别应该是:
  printf("%p,%p\n",a,*a);
  当然,由于输出的结果是“实现定义的”,所以在不同的实现中的输出结果的形式可能并不相同。
  4 结束语
  根据前面的分析和讨论,可以得到如下的结论:
  1. 指针数据类型并不等同于任何整数类型。
  2. 用“%d”、“%o”、 “%x”(或“%X”)及“%u”这几种格式输出指针类型的值是错误的未定义行为。
  3. 应该用“%p”转换格式输出指针类型数据的值。
  本文指出的错误并非是今天才出现的,事实上二十年时间前出版的C语言教材[5]就已经存在这两种错误。作为教材,应该遵循标准,教给学生具有一般性的通用性的知识。然而,近二十年间这个错误竟然没有得到改正,这是非常令人震惊的事情。在此期间,不少以[4]、[5]为参考编写的C语言教材或书籍中同样也存在类似的错误,可见这两个错误的影响之广泛及深远。
  为此本文正式指出这个错误并予以更正,希望这个错误不至于再以讹传讹地流传下去。
  
  参考文献:
  [1]. Brian W.Kernighan, Dennis M.Ritchie. C程序设计语言.清华大学出版社,1998
  [2]. 国家技术监督局,GB/T 15272-94 程序设计语言C,1994
  [3]. International Organization for Standardization,ISO/IEC 9899:1999.[ISO]
  [4]. 谭浩强,《C程序设计》(第四版),清华大学出版社,2010年6月第4版
  [5]. 谭浩强,《C程序设计》(第一版),清华大学出版社,1991年7月第1版

推荐访问:指针 输出 正确 怎样正确输出指针类型数据 python输出数据类型 python数据类型有哪些