C++内存相关问题总结

1、以下为WindowsNT 32位C++程序,请计算下面sizeof的值
  char str[] = "hello";
  char* p = str;
  int n = 10;
  // 请计算
  sizeof(str) = ?
  sizeof(p) = ?
  sizeof(n) = ?
  void Func(char str[100])
  {
  	// 请计算
  	sizeof(str) = ?
  }
  void* p = malloc(100);
  // 请计算
  sizeof(p) = ?

【参考答案】:
sizeof(str) = 6
sizeof()计算的是数组的所占内存的大小包括末尾的 ‘\0’

sizeof ( p)= 4
p为指针变量,32位系统下大小为 4 bytes

sizeof(n) = 4

void Func(char str[100])
{
​ sizeof(str) = 4
}

函数的参数为字符数组名,即数组首元素的地址,大小为指针的大小

void* p = malloc(100);
sizeof( p) = 4
p指向malloc分配的大小为100 byte的内存的起始地址,sizeof§为指针的大小,而不是它指向内存的大小

2、分析运行下面的Test函数会有什么样的结果
 void GetMemory1(char* p)
{
    p = (char*)malloc(100);
}

void Test1(void)
{
    char* str = NULL;
    GetMemory1(str);
    strcpy(str, "hello world");
    printf(str);
}

char *GetMemory2(void)
{
    char p[] = "hello world";
    return p;
}

void Test2(void)
{
    char *str = NULL;
    str = GetMemory2();
    printf(str);
}

void GetMemory3(char** p, int num)
{
    *p = (char*)malloc(num);
}

void Test3(void)
{
    char* str = NULL;
    GetMemory3(&str, 100);
    strcpy(str, "hello");
    printf(str);
}

void Test4(void)
{
    char *str = (char*)malloc(100);
    strcpy(str, "hello");
    free(str);
    if(str != NULL)
    {
        strcpy(str, "world");
        cout << str << endl;
    }
}

【参考答案】:

Test1, 程序崩溃。因为GetMemory1并不能传递动态内存,Test1函数中的 str一直都是NULL。strcpy(str, "hello world")将使程序奔溃

Test2,可能是乱码。因为GetMemory2返回的是指向“栈内存”的指针,该指针的地址不是NULL,使其原现的内容已经被清除,新内容不可知。

Test3,能够输出hello, 内存泄露。GetMemory3申请的内存没有释放

Test4, 篡改动态内存区的内容,后果难以预料。非常危险。因为 free(str);之后,str成为野指针,if(str != NULL)语句不起作用。

3、实现内存拷贝函数 char *strcpy(char* strDest, const char* strSrc);

【参考答案】:

(1)、函数实现
char * strcpy(char *dst,const char *src)   //[1]
{
    assert(dst != NULL && src != NULL);    //[2]

    char *ret = dst;  //[3]

    while ((*dst++=*src++)!='\0'); //[4]

    return ret;
}

[1]const修饰

源字符串参数用const修饰,防止修改源字符串。

[2]空指针检查

(A)不检查指针的有效性,说明答题者不注重代码的健壮性。

(B)检查指针的有效性时使用 assert(!dst && !src);

char *转换为bool即是类型隐式转换,这种功能虽然灵活,但更多的是导致出错概率增大和维护成本升高。

©检查指针的有效性时使用 assert(dst != 0 && src != 0);

直接使用常量(如本例中的0)会减少程序的可维护性。而使用NULL代替0,如果出现拼写错误,编译器就会检查出来。

[3]返回目标地址

(A)忘记保存原始的strdst值。

[4]’\0’

(A)循环写成while (*dst++=*src++);明显是错误的。

(B)循环写成while (*src!='\0') *dst++=*src++;

循环体结束后,dst字符串的末尾没有正确地加上’\0’。

(2).为什么要返回char *?

返回dst的原始值使函数能够支持链式表达式。

链式表达式的形式如:

int l=strlen(strcpy(strA,strB));

又如:

char * strA=strcpy(new char[10],strB);

返回strSrc的原始值是错误的。

其一,源字符串肯定是已知的,返回它没有意义。

其二,不能支持形如第二例的表达式。

其三,把const char *作为char *返回,类型不符,编译报错。

3.假如考虑dst和src内存重叠的情况,strcpy该怎么实现

char s[10]="hello";

strcpy(s, s+1); //应返回ello,

//strcpy(s+1, s); //应返回hhello,但实际会报错,因为dst与src重叠了,把’\0’覆盖了

所谓重叠,就是src未处理的部分已经被dst给覆盖了,只有一种情况:src<=dst<=src+strlen(src)

C函数memcpy自带内存重叠检测功能,下面给出memcpy的实现my_memcpy。

char * strcpy(char *dst,const char *src)
{
    assert(dst != NULL && src != NULL);

    char *ret = dst;

    my_memcpy(dst, src, strlen(src)+1);

    return ret;
}

my_memcpy的实现如下

char *my_memcpy(char *dst, const char* src, int cnt)
{
    assert(dst != NULL && src != NULL);

    char *ret = dst; 

    if (dst >= src && dst <= src+cnt-1) //内存重叠,从高地址开始复制
    {
        dst = dst+cnt-1;
        src = src+cnt-1;
        while (cnt--)
            *dst-- = *src--;
    }
    else    //正常情况,从低地址开始复制
    {
        while (cnt--)
            *dst++ = *src++;
    }
    
    return ret;
}
4、按照下面描述的要求写程序

已知String的原型为

class String 
{ 
public: 
     String(const char *str = NULL);         //构造函数 
     String(const String &other);            //拷贝构造函数 
     ~ String(void);                         //析构函数 
     String & operate =(const String &other);//赋值函数 
private: 
     char *m_data;// 用于保存字符串 
}; 

请编写上述四个函数。 此题考察对构造函数赋值运算符实现的理解。实际考察类内含有指针的构造函数赋值运算符函数写法。

参考答案:

//构造函数  
String::String(const char *str)  
{  
    if(str==NULL)  
    {  
        m_data = new char[1];    //对空字符串自动申请存放结束标志'\0'  
        *m_data = '\0';  
    }      
    else  
    {  
        int length = strlen(str);  
        m_data = new char[length+1];
        strcpy(m_data, str);  
    }  
}   

//析构函数  
String::~String(void)  
{  
    delete [] m_data; // 或delete m_data;  
}  

//拷贝构造函数  
String::String(const String &other)
{       
    int length = strlen(other.m_data);  
    m_data = new char[length+1];
    strcpy(m_data, other.m_data);      
}   

//赋值函数  
String &String::operate =(const String &other)
{       
    if(this == &other)                    //检查自赋值  
        return *this;     
    delete []m_data;                    //释放原有的内存资源  
    int length = strlen(other.m_data);        
    m_data = new char[length+1];          //对m_data加NULL判断  
    strcpy(m_data, other.m_data);     
    return *this;                    //返回本对象的引用    
}  
5、说一说进程的地址空间分布

【参考答案】:

对于一个进程,其空间分布如下图所示

img

如上图,从高地址到低地址,一个程序由命令行参数和环境变量、栈、文件映射区、堆、BSS段、数据段、代码段组成。

命令行参数和环境变量: 命令行参数是指从命令行执行程序的时候,给程序的参数。
栈区:存储局部变量、函数参数值。栈从高地址向低地址增长。是一块连续的空间。
文件映射区,位于堆和栈之间。
堆区:动态申请内存用。堆从低地址向高地址增长。
BSS 段:存放程序中未初始化全局变量和静态变量 的一块内存区域。
数据段:存放程序中已初始化全局变量和静态变量 的一块内存区域。
代码段:存放程序执行代码的一块内存区域。只读,代码段的头部还会包含一些只读的常数变量。

6、说一说C与C++的内存分配方式?

【参考答案】:

1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在,如全局变量,static变量。

2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

3)从堆上分配(动态内存分配)程序在运行的时候用malloc或new申请任意多少的内存,程序员负责在何时用free或delete释放内存。动态内存的生存期自己决定,使用非常灵活。

7、new、delete、malloc、free关系

【参考答案】: delete会调用对象的析构函数,和new对应。free只会释放内存,new调用构造函数。malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。

8、为什么new/delete 、new []/delete[] 要配对使用?

【参考答案】:

如果是带有自定义析构函数的类类型,用 new [] 来创建类对象数组,而用 delete来释放会发生什么?用上面的例子来说明:

class A {};
A* pAa = new A[3];
delete pAa;

那么 delete pAa; 做了两件事:

  • 调用一次 pAa 指向的对象的析构函数;
  • 调用 operator delete(pAa);释放内存。

显然,这里只对数组的第一个类对象调用了析构函数,后面的两个对象均没调用析构函数,如果类对象中申请了大量的内存需要在析构函数中释放,而你却在销毁数组对象时少调用了析构函数,这会造成内存泄漏。

上面的问题你如果说没关系的话,那么第二点就是致命的了!直接释放pAa指向的内存空间,这个总是会造成严重的 段错误,程序必然会奔溃!因为分配的空间的起始地址是 pAa 指向的地方减去 4 个字节的地方。你应该传入参数设为那个地址!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值