前文回顾:C 编程新手错误语录 (一)
(13)“整型变量仅仅意味着一个整数”
当我们还是一个新手,看整型就是整数;
当我们成为高手,看什么都是整型。
整型,在所有C/C++基本数据类型中最富有艺术魅力和奇幻色彩。
我们从某著名论坛的一篇帖子开始一窥整型的奥妙。
问:Vxworks操作系统启动一个任务的函数是taskSpawn(char* name, int priority, int options, int stacksize, FUNCPTR function, int arg1,.. , int arg10),它只接受整型参数,我该怎么办才能给它传一个结构体(在32位PowerPC平台下)?
答:可以传入结构体的指针,在32位PowerPC平台下,指针本质上就是一个32位整数,在函数体内将整型强制转化为结构体指针就可访问结构体的每一个元素。
如:
//启动任务1 taskSpawn(“task1”, 180, NULL, 10000, Task1Fun, &pStructAr,0,0,0,0,0,0,0,0,0); //task1函数 Task1Fun ( int arg1 ) { struct_x * pStructx = (struct_x *) arg1; //将整型强制转化为结构体指针 … } 在此提出“泛整型”的概念,(unsigned)char、(unsigned)short int、(unsigned)int、(unsigned)long int等都属于这个范畴,指针必然属于“泛整型”的范围。用指针的高超境界,也为将其看做一个“泛整型”。
看看软件的详细设计文档,其数据结构定义部分经常看到“INT8、UINT8、INT16、UINT16、INT32、UINT32、INT64、UINT64”或“BYTE、WORD、DWORD”等数据类型,它们在本质上都是(unsigned)char、(unsigned)short int、(unsigned)int、(unsigned)long int宏定义的结果,都属于“泛整型”。所以,“泛整型”的概念真实地体现在日常的软件设计当中。 正因为各种指针类型在本质上都是“泛整型”,因此它们可以互相转化:
int a, b; memset( (char*) &a, (char*) &b, sizeof(int) );
等价于:
int a, b; a = b;
从来没有人会用memset( (char*) &a, (char*) &b, sizeof(int) )来代替a = b,这里只是为了说明问题。下面的代码则经常用到:
int *p = (int *) malloc(100*sizeof(int)); memset ( p, 0, 100*sizeof(int) ); //将申请的内存空间清0
我们看memset的函数原型为:
void * memset ( void * buffer, int c, size_t num );
实际上它接受的第一个参数是无类型指针,在memset函数体内,其它任意类型的指针都向void *转化了。类似的内存操作函数memcpy所接受的源和目的内存地址也是无类型指针。
char *转化为int *后的值虽然不变(还是那个地址),但是其++、--等操作的含义却发生了变化,这也是要注意的。
char *p; ++p;
与
char *p; ++(int *)p;
的结果是不一样的,前者的p值加了1,而后者的则增加了sizeof(int)。
下面来剥Windows程序设计中消息传递函数两个参数的皮,看看它们究竟是什么:
typedef UINT WPARAM; typedef LONG LPARAM;
原来,WPARAM和LPARAM其实都属于“泛整型”,所以不要报怨消息处理函数只能接受“泛整型”。实际上,从指针的角度上来讲,在C/C++中,可以获得任何类型实例(变量、结构、类)的指针,所以Windows的消息处理函数实际上可以接受一切类型的参数。
惊天动地一句话:“泛整型”可表征一切。 (14)“值传递一定不会改变参数”
理论而言,值传递的确不会改变参数的内容。但是,某年某月的某一天,隔壁office的硕士mm写了这么一段程序,参数的值却被改变了:
int n = 9; char a[10]; example ( n, a ); //调用函数example(int n,char *pStr) printf (“%d”, n ); //输出结果不是9
大概整个office的人都被搞懵了,都说编译器瞎搞,有问题。找到笔者,笔者凭借以往的经常,一眼就看出来不是什么编译器出错,而是在函数example内对字符串a的访问越界!
当在函数example内对a的访问越界后,再进行写操作时,就有可能操作到了n所在的内存空间,于是改变了n的值。
给出这个语录,并非为了推翻“值传递不会改变参数”的结论,而是为了从侧面证明在C/C++语言中,数组越界是多么危险的错误!
下面的两个函数有明显的数组越界:
void example1() { char string[10]; char* str1 = "0123456789"; strcpy( string, str1 ); } void example 2(char* str1) { char string[10]; if( strlen( str1 ) <= 10 ) { strcpy( string, str1 ); } }
而这个函数的越界就不这么明显:
void example3() { char string[10], str1[10]; int i; for(i=0; i<10; i++) { str1[i] = 'a'; } strcpy( string, str1 ); } 其实,这个函数危险到了极点。因为对于strcpy函数而言,拷贝的时候要碰到’\0’才结束,str1并没有被赋予结束符,因而你根本就不知道strcpy( string, str1 )的结果究竟会是拷贝多大一片内存!
遗憾的是,C/C++永远不会在编译和连接阶段提示数组越界,它只会在运行阶段导致程序的崩溃。
数组越界,是大多数C/C++编程新手常犯的错误,而它又具有极大的隐蔽性,新手们一定要特别注意。 (15)“C不高级,学C++、JAVA、C#才够味”
也许谭浩强老师的C语言教材是绝大多数高校学生学习的第一门编程课程,所以在许多学生的心目中,觉得C是一种入 [1] [2] 下一页 |