位操作 ‘野指针’free()的实质 volatile关键字 Strcpy / Strncpy 函数 及一些题目

本文探讨了位操作技巧,如计数二进制中1的数量,并深入解析野指针的概念及其潜在风险。同时,文章介绍了如何正确使用free函数释放内存,避免内存泄漏。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

位操作

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 了

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值