初步了解内存

1.数据存储

    1.编译器会根据数据类型的不同,分配大小不同的内存。(就相对于看字节数吧)
    2.整型数据:有三种表示⽅法,即原码、反码和补码。
        原码:直接将⼆进制按照正负数的形式翻译成⼆进制就可以。
        反码:将原码的符号位不变,其他位依次按位取反就可以得到了。
        补码:反码+1就得到补码。
        正数的原、反、补码都相同。
        对于整形来说:数据存放内存中其实存放的是补码。
        原因:在计算机系统中,数值⼀律⽤补码来表示和存储。原因在于,使⽤补码,可以将符号位和数值域统⼀处理;同时,加法          和减法也可以统⼀处理( CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路
    3.浮点数据类型:这个比较麻烦,就简单介绍一下
        对于32位的浮点数:最高的1位是符号位,接着的8位是指数,剩下的23位为有效数字。
        对于64位的浮点数:最高的1位是符号位,接着的11位是指数,剩下的52位为有效数字。

2.大小端(重点*****)

    1.⼤端(存储)模式,是指数据的低位保存在内存的⾼地址中,⽽数据的⾼位保存在内存的低地址中。
    2.⼩端(存储)模式,是指数据的低位保存在内存的低地址中,⽽数据的⾼位保存在内存的⾼地址中。
    3.为什么会有⼤⼩端模式之分呢?

  •         这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着⼀个字节,⼀个字节为8bit。但是在C语⾔中除了8bit的char之外,还有16bit的short型, 32bit的long型(要看具体的编译器),另外,对于位数⼤于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度⼤于⼀个字节,那么必然存在着⼀个如果将多个字节安排的问题。因此就导致了⼤端存储模式和⼩端存储模式。例如⼀个16bit的short型x,在内存中的地址为0x0010, x的值为0x1122,那么0x11为⾼字节, 0x22为低字节。对于⼤端模式,就将0x11放在低地址中,即0x0010中, 0x22放在⾼地址中,即0x0011中。⼩端模式,刚好相反。我们常⽤的X86结构是⼩端模式。很多的ARM, DSP都为⼩端模式。有些ARM处理器还可以由硬件来选择是⼤端模式还是⼩端模式。PowerPC、KEIL C51与网络字节序都是采用的大端模式。

    4.使用程序判断大小端

  • 使用联合判断:因为联合底层是公用同一块内存的,也就是说这个联合c所占用的内存就是4个字节,b取低位如果是1就是小端,如果不是1就是大端。画个图理解理解(其中联合是从低地址开始放数据的)
  • int Judgement() {
            union {
                int a;
                char b;        
            }c;
            c.a = 1;
            return (c.b == 1); //小端返回TRUE,大端返回FALSE
        }

  • 使用指针判断:原理非常相似上面:就是char*类型的指针只能接受&i,需要对i进行强转数据会丢失一部分

  • void Judgement(void) {
            int i = 1;
            unsigned char* p;
            p = (unsigned char *)&i;
            if (*p) {
                printf("little_end");
            } else {
                printf("big_end");
            }
        }

  5.涉及接受一下C++中内存地址分配:

  1. 内存地址是从高地址到低地址进行分配的。
  2. 函数参数列表的存放方式是,先对最右边的形参分配地址,后对最左边的形参分配地址。
  3. Little-endian模式的CPU对操作数的存放方式是从低字节到高字节的。
  4. Big-endian模式的CPU对操作数的存放方式是从高字节到低字节的。
  5. 联合体union的存放顺序是所有成员都从低地址开始存放。
  6. 一个变量的地址是由它所占内存空间中的最低位地址表示的。
  7. 堆栈的分配方式是从高内存地址向低内存地址分配的。

3.C/C++程序内存的分配 (重点*****)

     ⼀个由C/C++编译的程序占⽤的内存分为以下⼏个部分:
    1.栈区---由编译器⾃动分配释放,存放为运⾏函数⽽分配的局部变量、函数参数、返回数据、返回地址等。其操作⽅式类似于数据结构中的栈,而且栈是向下增长的。
    2.内存映射段---是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存,做进程间通信。  
    3.堆区---⼀般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配⽅式类似于链表。堆是可以上增长的。
    4.数据段---存放全局变量、静态数据、常量。程序结束后由系统释放。
    5.代码端---可执行的代码/只读常量。

4.动态内存函数的介绍:(这里就用讲一组常用C语言常用的malloc和free,C++的new和delete) 

C语⾔提供了⼀个动态内存开辟的函数:

void* malloc (size_t size);

这个函数向内存申请⼀块连续可⽤的空间,并返回指向这块空间的指针。
- 如果开辟成功,则返回⼀个指向开辟好空间的指针。
- 如果开辟失败,则返回⼀个NULL指针,因此malloc的返回值⼀定要做检查。
- 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使⽤的时候使⽤者
⾃⼰来决定。
- 如果参数 size 为0, malloc的⾏为是标准是未定义的,取决于编译器。

C语⾔提供了另外⼀个函数free,专⻔是⽤来做动态内存的释放和回收的,函数原型如下:

void free (void* ptr);

free函数⽤来释放动态开辟的内存。
- 如果参数 ptr 指向的空间不是动态开辟的,那free函数的⾏为是未定义的。
- 如果参数 ptr 是NULL指针,则函数什么事都不做 。

 C++语言提供的new和delete(其实底层new还是用malloc申请空间只是需要调用构造函数,delete要调用析构函数):

new和delete是用户进行动态内存申请和释放的操作符,operator new和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。在这里就实现一下

new函数的实现:

   void *_CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc) {
        void *p;
        while ((p = malloc(size)) == 0) {
            if (_callnewh(size) == 0) {
                static const std::bad_alloc nomem;
                _RAISE(nomem);
            }        
        }
        return (p);
    }

 delete函数的实现:

//该函数最终是通过free来释放空间的

void operator delete(void *pUserData) {
    _CrtMemBlockHeader* pHead;
    RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
    
    if (pUserData == NULL)
        return;
    _mlock(_HEAP_LOCK);
    _TRY
    
    pHead = pHdr(pUserData);
    
}

   *****这里有个重要的知识就是设计一个类,该类只能在堆上创建对象       

在C++中,创建类的对象有两种方法,一种是静态建立,A a; 另一种是动态建立,调用new 操作符。

    静态建立一个类对象,是由编译器为对象在栈空间中分配内存,是通过直接移动栈顶指针,挪出适当的空间,然后在这片内存空间上调用构造函数形成一个栈对象。使用这种方法,直接调用类的构造函数。

    动态建立类对象,是使用new运算符将对象建立在堆空间中。这个过程分为两步,第一步是执行operator new()函数,在堆空间中搜索合适的内存并进行分配;第二步是调用构造函数构造对象,初始化这片内存空间。这种方法,间接调用类的构造函数。

    1.只能在堆上创建对象的类:
     那就是动态建立类的对象,使用new操作符来完成。 
        所以可以这样做,将该类的构造函数和析构函数权限设为protected,(可以让该类可以被继承),然后定义两个static 函数来调用new ,delete 来创建和销毁对象。

class A
{
protected:
    A(){}
    ~A(){}
public:
    static A* Create()
    {
        return new A();
    }
    static void Destroy(A* p)
    {
        delete p;
        p = NULL;
    }
};

   2.将析构函数声明为私有的:(在堆上创建)
    对象建立在栈上面时,是由编译器分配空间的,调用构造函数来构造栈对象,当对象使用完之后,编译器会调用析构函数来释放栈对象所占的空间,编译器管理了对象的整个生命周期,编译器为对象分配空间的时候,只要是非静态的函数都会检查,包括析构函数,但是此时析构函数不可访问,编译器无法调用类的析构函数来释放内存,那么编译器将无法在栈上为对象分配内存。

class a
{
public :
    a(){}
    void destory()
    {
        delete this;
    }
private:
    ~a(){};
};

方法二两个缺点:(1)无法解决继承问题,因为通常情况之下a作为基类,一般析构函数要设为vitual,然后子类重写,已实现多态,因此析构函数不能设为private,不过c++还有protected访问控制方式,将析构函数设置为protected,这样子类可以访问,但是类外无法访问。
    (2)使用不方便,不统一,因为你使用了new创造了对象,但是不能使用delete释放对象,必须使用destory函数,这种方式比较怪异,所以我们也可以将构造函数设置为protected,同时提供另一public static create()函数来进行替代new。这样 create()创建对象在堆上, destory()释放内存。 

    3.只能在栈上创建对象的类

class B
{
private:
    void * operator new(size_t size){}
    void operator delete(void *ptr){}

public:
    B(){}
    ~B(){}
};

malloc/free和new/delete的区别
malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:
1. malloc和free是函数,new和delete是操作符
2. malloc申请的空间不会初始化,new可以初始化
3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可
4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间
后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理
7. new/delete比malloc和free的效率稍微低点,因为new/delete的底层封装了malloc/free 

5.堆和栈究竟有什么区别? 

主要的区别由以下几点: 
1、管理方式不同;
     对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生内存泄露
2、空间大小不同;
     一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的(但是可以在编译器上设置空间大小打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit。注意:Reserve最小值为4Byte;commit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。)
3、能否产生碎片不同; 
     对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在它弹出之前,在它上面的后进的栈内容已经被弹出。
4、生长方向不同;
    对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。  
5、分配方式不同; 
    堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,它的动态分配是由编译器进行释放,不需要我们手工实现。 
6、分配效率不同; 
    栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高
    堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。

总结:看了好多博客和自己看书总结的笔记,浓缩的,感觉差不多讨论全了,有不足的地方希望大佬多多评论

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值