位操作
x&(-x)取最低位的1
x&(x-1)去掉最低位的1 v & (v -1 )每次能消去二进制表示中最后一位1
x^y^y 异或后 为 x
“野指针”不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if语句对它不起作用。
“野指针”的成因主要有两种:
(1)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如
char *p = NULL;
char *str = (char *) malloc(100);
(2)指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。参见7.5节。
别看free和delete的名字恶狠狠的(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。
用调试器跟踪示例7-5,发现指针p被free以后其地址仍然不变(非NULL),只是该地址对应的内存是垃圾,p成了“野指针”。如果此时不把p设置为NULL,会让人误以为p是个合法的指针。
如果程序比较长,我们有时记不住p所指的内存是否已经被释放,在继续使用p之前,通常会用语句if (p != NULL)进行防错处理。很遗憾,此时if语句起不到防错作用,因为即便p不是NULL指针,它也不指向合法的内存块。
char *p = (char *) malloc(100);
strcpy(p, “hello”);
free(p); // p 所指的内存被释放,但是p所指的地址仍然不变
…
if(p != NULL) // 没有起到防错作用
{
strcpy(p, “world”); // 出错
}
示例7-5 p成为野指针
(3)指针操作超越了变量的作用范围。这种情况让人防不胜防,示例程序如下:
class A
{
public:
void Func(void){ cout << “Func of class A” << endl; }
};
void Test(void)
{
A *p;
{
A a;
p = &a; // 注意 a 的生命期
}
p->Func(); // p是“野指针”
}
函数Test在执行语句p->Func()时,对象a已经消失,而p是指向a的,所以p就成了“野指针”。但奇怪的是我运行这个程序时居然没有出错,这可能与编译器有关。
free()的实质
先说申请空间。
第一,当我们申请一个变量后,在这里特意以动态申请资源为例:char* p=realloc(10)。当我们申
请好资源后,系统负责为我们维护——不再分配给其他对象。
这里要注意,如果我们不再利用这块资源,我们应该free掉,否则就会发生“内存泄漏”,说白了就
是浪费掉这块内存了(不用就跟系统说一声么,让它收回去)。
第二,也是重点。当我们用free释放资源后,利用官方的话就是指针的资源“内存释放”,到底发生
了什么?
1。free后这个指针的值不变,也就是仍然指向那块空间。
2。这块资源发生了什么?
a:系统不再维护这个资源,随时可能分配给别的对象。
b:这块资源短时间内其实内容并不变,只是开始的4个字节被替为'/0'而已,这样
造成一个假相,好像在free后这个指针指向空值。其实不然啊!
#include<string.h>
int main()
{
char *p = (char *) malloc(10);//malloc后,内存会为我们维护这10个字节
int i = 0;
strcpy(p, "helloworld"); //越界的字符(个数超过10)得不到维护,可能被修改
printf("p:%s/n",p);
free(p); // p 所指的内存被释放,但是p所指的地址仍然不变
//内存被释放在实际操作中是将指针所指的开始4个字符清为'/0',
//而后面的字符都不变,当然这样可能会在以后出错,因为free以
//后这10个字节的内存已经不能维护了,随时可能被分配给别的对象用,危险啊!
while(i!=9)
{
printf("p:%c/n",p[i]);
i++;
}
return 1;
}
free 只是释放的str指向的内存空间,它本身的值还是存在的.
所以free之后,有一个好的习惯就是将str=NULL.
1》关键字volatile有什么含意?并举出三个不同的例子?
提示编译器对象的值可能在编译器未监测到的情况下改变。当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。从而可以提供对特殊地址的稳定访问。
通过插入汇编代码,测试有无volatile关键字,对程序最终代码的影响:
首先,用classwizard建一个win32 console工程,插入一个voltest.cpp文件,输入下面的
代码:
#i nclude <stdio.h>
void main()
{
int i=10;
int a = i;
printf("i= %d/n",a);
//下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道
__asm {
mov dword ptr [ebp-4], 20h
}
int b = i;
printf("i= %d/n",b);
}
然后,在调试版本模式运行程序,输出结果如下:
i = 10
i = 32
然后,在release版本模式运行程序,输出结果如下:
i = 10
i = 10
输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。
下面,我们把 i的声明加上volatile关键字,看看有什么变化:
#i nclude <stdio.h>
void main()
{
volatile int i=10;
int a = i;
printf("i= %d/n",a);
__asm {
mov dword ptr [ebp-4], 20h
}
int b = i;
printf("i= %d/n",b);
}
分别在调试版本和release版本运行程序,输出都是:
i = 10
i = 32
这说明这个关键字发挥了它的作用!
2》 Strcpy / Strncpy的工作方式
Strcpy的函数原型:
char *strcpy(char *dst, const char *src)
函数功能:
将字符串src拷贝到字符串dst中去。
用法解析:
在执行字符串拷贝之前,“用户需要保证”dst指向的空间足够大。否则的话,可能会产生意想不到的后果。
如果dst所指向的空间不足以存储src中的字符串的话,不要以为只是遗失了src中存储不下的字符串这么简单。
在VC的库函数中,strcpy()的定义如下:
char * __cdecl strcpy(char * dst, const char * src)
{
char * cp = dst;
while( *cp++ = *src++ )
; /* Copy src over dst */ /*注意这行有个分号*/
return( dst );
}
用法解析:
这个函数和strcpy类似,当src的长度大于dst申请的空间的时候,情况和strcpy一样;
如果第3个参数count的值大于src中字符串的长度的话,就会将字符串src拷贝到dst中,返回函数。
示例程序:
#include <iostream>
// #include <conio.h> // for getch()
#include <string.h> // for stycpy()
using namespace std;
int main()
{
int i = 0;
char dst[11] = "1234567890";
char src[15] = {'1','2','3','4','5','/0','7','8','9','0','1','2','3','4'};
strcpy( dst, src );
cout<<"strcpy()"<<endl;
cout<<dst<<endl<<endl;
strncpy( dst, src, 14 ); // cont = 14
cout<<"strncpy()"<<endl;
cout<<dst<<endl<<endl;
// getch();
return 0;
}
程序执行结果:
strcpy()
12345
strncpy()
12345
3> 内存
a>#include <stdio.h>
char *GetMemory( void )
{
char p[] = "hello world";
return p;
} gcc调试结果: gcc -o test test.c
test.c: In function ‘GetMemory’:
test.c:5: warning: function returns address of local variable
void Test( void )
{
char *str = NULL;
str = GetMemory();
printf( str );
}
int main(int argc,int* argv[]){
Test();
}
试题a中
char p[] = "hello world"; return p; |
的p[]数组为函数内的局部自动变量,在函数返回后,内存已经被释放。这是许多程序员常犯的错误,其根源在于不理解变量的生存期。
4> 位操作题
求函数返回值,输入x=9999;
int func(x)
{
int countx = 0;
while(x)
{
countx ++;
x = x&(x-1);
}
return countx;
}
【试题解析】
解这道题的时候,如果拿出一个二进制数来分析就会容易的多了,x=x&(x-1)实际上就是把x的二进制形式的最后一个“1”变成“0”,x的二进制形式有多少个“1”循环就执行多少次。
9999/256 = 39 余 15,把这两个数分别转化一下就很快了
39 = 32 + 4 + 2 +1 = 00100111
15 = 0F = 00001111
所以 9999=0010011100001111,共有8个1,答案就是 8 了