分类: 嵌入式
2009-12-01 16:02:08
第七章 指针和结构类型的关系
可以声明一个指向结构类型对象的指针。
例十一:
struct MyStruct
{
int a;
int b;
int c;
}
MyStruct ss={20,30,40}; //声明了结构对象ss,并把ss的三个成员初始
化为20,30和40。
MyStruct*ptr=&ss; //声明了一个指向结构对象ss的指针。它的类型是
MyStruct*,它指向的类型是MyStruct。
int*pstr=(int*)&ss;//声明了一个指向结构对象ss的指针。但是它的
类型和它指向的类型和ptr是不同的。
请问怎样通过指针ptr来访问ss的三个成员变量?
答案:
ptr->a;
ptr->b;
ptr->c;
又请问怎样通过指针pstr来访问ss的三个成员变量?
答案:
*pstr;//访问了ss的成员a。
*(pstr+1);//访问了ss的成员b。
*(pstr+2)//访问了ss的成员c。
呵呵,虽然我在我的MSVC++6.0上调式过上述代码,但是要知道,这样使用pstr来访问结构成员是不正规的,为了说明为什么不正规,让我们看看怎样通过指针来访问数组的各个单元:
例十二:
intarray[3]={35,56,37};
int*pa=array;
通过指针pa访问数组array的三个单元的方法是:
*pa; //访问了第0号单元
*(pa+1);//访问了第1号单元
*(pa+2);//访问了第2号单元
从格式上看倒是与通过指针访问结构成员的不正规方法的格式一样。
所有的C/C++编译器在排列数组的单元时,总是把各个数组单元存放在连续的存储区里,单元和单元之间没有空隙。但在存放结构对象的各个成员时,在某种编译环境下,可能会需要字对齐或双字对齐或者是别的什么对齐,需要在相邻两个成员之间加若干个"填充字节",这就导致各个成员之间可能会有若干个字节的空隙。
所以,在例十二中,即使*pstr访问到了结构对象ss的第一个成员变量a,也不能保证*(pstr+1)就一定能访问到结构成员b。因为成员a和成员b之间可能会有若干填充字节,说不定*(pstr+1)就正好访问到了这些填充字节呢。这也证明了指针的灵活性。要是你的目的就是想看看各个结构成员之间到底有没有填充字节,嘿,这倒是个不错的方法。
通过指针访问结构成员的正确方法应该是象例十二中使用指针ptr的方法。
第八章 指针和函数的关系
(1)可以把一个指针声明成为一个指向函数的指针。
typedef int(*FUNC)(char*,int);//标准做法定义一个函数指针
FUNC pfun;
int fun(char*,int);
pfun=fun;
....
....
inta=(*pfun)("abcdefg",7);//通过函数指针调用函数。
(2) CALLBACK函数。可以把指针作为函数的形参。在函数调用语句中,可以用指针表达式来作为实参。
#include "stdafx.h"
#include
#define DF(F) int F(){ cout<<"this is in function "<<#F<
}
//声明定义DF(F)替代 int F();函数;
DF(a); DF(b); DF(c); DF(d); DF(e); DF(f); DF(g); DF(h); DF(i); //声明定义函数 a b c d e f g h i
// int (*pfunc)(); //一个简单函数指针的声明
typedef int(*FUNC)(); //一个函数指针类型的声明
FUNC ff[] = {a,b,c,d,e,f,g,h,i}; //声明一个函数指针数组,并初始化为以上声明的a,b,c,d,e,f,g,h,i函数
FUNC func3(FUNC vv){ //定义函数func3,传入一个函数指针,并且返回一个同样类型的函数指针
vv();
return vv;
}
/*FUNC func4(int (*vv)()){ //func3的另一种实现
vv();
return vv;
}*/
int main(){
for(int i=0;i
cout<
return 0;
}
到目前为止,我们只讨论了函数指针及回调而没有去注意ANSI C/C++的编译器规范。许多编译器有几种调用规范。如在Visual C++中,可以在函数类型前加_cdecl,_stdcall或者_pascal来表示其调用规范(默认为_cdecl)。C++ Builder也支持_fastcall调用规范。调用规范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。
第九章 指针的安全问题
看下面的例子:
例十七:
chars='a';
int*ptr;
ptr=(int*)&s;
*ptr=1298;
指针ptr是一个int*类型的指针,它指向的类型是int。它指向的地址就是s的首地址。在32位程序中,s占一个字节,int类型占四个字节。最后一条语句不但改变了s所占的一个字节,还把和s相临的高地址方向的三个字节也改变了。这三个字节是干什么的?只有编译程序知道,而写程序的人是不太可能知道的。也许这三个字节里存储了非常重要的数据,也许这三个字节里正好是程序的一条代码,而由于你对指针的马虎应用,这三个字节的值被改变了!这会造成崩溃性的错误。
让我们再来看一例:
例十八:
1 chara;
2 int *ptr=&a;
...
...
3 ptr++;
4 *ptr=115;
该例子完全可以通过编译,并能执行。但是看到没有?第3句对指针ptr进行自加1运算后,ptr指向了和整形变量a相邻的高地址方向的一块存储区。这块存储区里是什么?我们不知道。有可能它是一个非常重要的数据,甚至可能是一条代码。而第4句竟然往这片存储区里写入一个数据!这是严重的错误。所以在使用指针时,程序员心里必须非常清楚:我的指针究竟指向了哪里。在用指针访问数组的时候,也要注意不要超出数组的低端和高端界限,否则也会造成类似的错误。
在指针的强制类型转换:ptr1=(TYPE*)ptr2中,如果sizeof(ptr2的类型)大于sizeof(ptr1的类型),那么在使用指针ptr1来访问ptr2所指向的存储区时是安全的。如果sizeof(ptr2的类型)小于sizeof(ptr1的类型),那么在使用指针ptr1来访问ptr2所指向的存储区时是不安全的。至于为什么,读者结合例十七来想一想,应该会明白的。
9.1 总线错误和段错误
有时编译程序时会遇到两个运行时错误:
bus error(core dumped) //总线错误(信息已转储)
segmentation fault(core dumped)段错误(信息已转储)
总线错误几乎都是由于未对齐的读或者写造成的。
例:
union { char a[10];
int I;
}u;
int *p = (int *)&(u.a[1]);
*p = 17; //p中未对齐的地址会引起一个总线错误!
数组和int 的联合确保数组a是按照int的4字节对齐的,所以“a+1”的地址肯定未按int对齐。然后我们试图往这个地址存储4个字节的数据,但这个访问只是按照单字节的char对齐,这就违反了规则。UNIX下会报错,MSVC中不汇报错,但运行结果异常。
段错误
例:
int *p = 0;
*p = 17;//段错误,访问了不该访问的地址
段错误是由于内存管理单元的异常所致,该异常通常是由于解除引用一个未初始化或非法值得指针引起的。UNIX下报错,MSVC不报错,运行时错误。
总结:以发生频率为序,最终可能导致段错误的常见编程错误是:
1. 坏指针值错误:在指针赋值之前就用它来引用内存,或者向库函数传送一个坏指针,或者在指针释放后再访问他的内容。记得free(p)后p = NULL;
2 . 改写错误:越过数组边界写数据,在动态分配的内存两端之外写入数据,或改写一些对管理数据结构。
3 . 指针释放引起的错误:释放同一内存块两次,或释放一块未曾使用malloc分配的内存,或释放仍在使用中的内存,或释放一个无效指针。
9 2 使用指针创建和使用动态数组
基本思路:用malloc()分配大块连续内存,像引用数组一样引用这块内存。
Char* dynamic = (char)malloc(100*sizeof(int));
Dynamic[7] = ‘a’;
realloc()实现内存块大小重新分配。
Dynamic = (char *)realloc(dynamic,20);
在使用malloc()和realloc()进行动态内存分配时存在的问题:
1, realloc函数可能引起内存拷贝,使系统运行速度慢下来,增加开销。
2, 若内存被拷贝到另一个地方,则元素地址不再有效。为避免麻烦,因该使用下标而不是元素地址。
3, 每次搜索该内存区是,编译器最好能够知道任意时刻该内存块的大小。