• linux高级编程day01 笔记


    1. malloc怎么分配空间

        malloc与new的关系

        看完下面的2再回答这个问题。

      2. linux对内存的结构描述

        a)         /proc/${pid}/         存放进程运行时候所有的信息。程序一结束,该目录就删掉了。

        b)        任何一个程序的内存空间其实分成4个基本部分。

                            i.              代码区

                            ii.              全局栈区

                           iii.              堆

                           iv.              局部栈

    小实验: 运行一个只包含while(1);的程序,然后另起一个终端,cd /proc下面的对应进程的pid目录,cat maps,查看到运行进程的内存空间分配情况。

    进程查看: ps aue

        c)         理解程序的变量与内存空间的关系

    小实验:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    int add(int a, int b)
    {
        return a+b;
    }
    int a1 = 1;
    static int a2 = 2;
    const int a3 = 3;
    main()
    {
        int b1 = 4;
        static b2 = 5;
        const b3 = 6;
        
        int *p1 = malloc(4);
        
        printf("a1:%p\n", &a1);
        printf("a2:%p\n", &a2);
        printf("a3:%p\n", &a3);
        printf("b1:%p\n", &b1);
        printf("b2:%p\n", &b2);
        printf("b3:%p\n", &b3);
        printf("p1:%p\n", p1);
        printf("main:%p\n", main);
        printf("add:%p\n", add);
        
        printf("%d\n", getpid());
        while(1);
    }

    把打印结果与/proc下对应目录中的maps文件比较。

    (代码区一般是ox8048000开头的区域。 )

    可以看到 a3全局常量在代码区(字面值神马的也是放在代码区)。 b3局部常量放在局部栈区。

    a1, a2, b2 则是放在全局栈区。

    main, add 在代码区。

    b1, b3在局部栈区。

    p1 在堆

    小实验:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    main()
    {
        int a1 = 10;
        int a2 = 20;
        int a3 = 30;
        
        int *p1 = malloc(4);
        int *p2 = malloc(4);
        int *p3 = malloc(4);
        
        printf("%p\n", &a1);
        printf("%p\n", &a2);
        printf("%p\n", &a3);
        printf("%p\n", p1);
        printf("%p\n", p2);
        printf("%p\n", p3);
        printf("%p\n", &a1);
        printf("%p\n", &a1);
        printf("%p\n", &a1);
        
        printf("%d\n", getpid());
        while(1);
    }

    运行结果如下:

    可以看到,a1, a2, a3的地址降序排列,相差4个字节。(栈  分配内存是直接压到栈顶)

    p1, p2, p3的地址升序排列,相差16个字节。(堆)

    小结:

    1. 内存分四个区。
    2. 各种变量对应存放区。
    3. 堆栈是一种管理内存的数据结构。

    查看程序的内存地址。

    回到问题1.

    看一个小实验:

    #include <stdio.h>
    #include <stdlib.h>
    
    int main()
    {
        int *p1 = malloc(4);
        int *p2 = malloc(4);
        int *p3 = malloc(4);
        
        *p1 = 1;
        *(p1+1) = 2;
        *(p1+2) = 3;    
        *(p1+3) = 4;
        *(p1+4) = 5;
        *(p1+5) = 6;
        *(p1+6) = 7;
        *(p1+7) = 8;
        *(p1+8) = 9;
        
        printf("%d\n", *p2);
        return 0;
    }

    运行结果是5.

    如果在程序中加一句话后:

    #include <stdio.h>
    #include <stdlib.h>
    
    int main()
    {
        int *p1 = malloc(4);
        int *p2 = malloc(4);
        int *p3 = malloc(4);
        
        *p1 = 1;
        *(p1+1) = 2;
        *(p1+2) = 3;    
        *(p1+3) = 4;
        *(p1+4) = 5;
        *(p1+5) = 6;
        *(p1+6) = 7;
        *(p1+7) = 8;
        *(p1+8) = 9;
        
        free(p1);    //比上面的程序多了这句话
        printf("%d\n", *p2);
        return 0;
    }

    则会发生错误。

    p1指向int型,应该只占用4个字节。可是实际上却占了16个字节,因为P1其实是链表里的一个节点,多的12个字节其实是保存的一些指向下一个节点,或者别的一些信息。我们在用*(p1+1) = 2;   *(p1+2) = 3;          *(p1+3) = 4;破坏这些信息的时候,不会报错,但是在使用这个节点(free(p))时,则会报错了。

    3. 理解malloc的工作原理

    malloc使用一个数据结构(链表)来维护分配的空间。链表的构成:分配的空间、上一个空间的地址、下一个空间的地址、以及本空间的信息等。对malloc分配的空间不要越界访问,因为容易破坏后台的链表维护结构,导致malloc/free/calloc/realloc不正常工作。

    4. C++的new与malloc的关系

    小实验:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main()
    {
        int *p1 = (int*)malloc(4);
        int *p2 = new int;
        int *p3 = (int *)malloc(4);
        int *p4 = new int;
        int *p5 = new int;
        int *p6 = new int;
        
        printf("%p\n", p1);
        printf("%p\n", p2);
        printf("%p\n", p3);
        printf("%p\n", p4);
        printf("%p\n", p5);
        printf("%p\n", p6);
        return 0;
    }

    运行结果:

    结论:new的实现使用的是malloc来实现的。

    区别:new使用malloc后,还要初始化空间。基本类型,直接初始化成默认值。 UDT类型调用指定的构造器

    推论:delete也是调用free实现。

    区别:delete会调用指定的析构器,然后再调用free()。

    new与new[]的区别:new只调用一个构造器初始化。new[]循环对每个区域调用构造器。

    delete与delete[]的区别:delete只调用一次析构函数,而delete则把数组中的每个对象的析构函数都调用一遍。

    malloc      new

    realloc      new() //定位分配

    calloc       new[]

    free          delete

    5. 函数调用栈空间分配与释放

    5.1 总结:

    1. 函数执行的时候有自己的临时栈。(C++中的成员函数有对象栈空间和函数栈空间两个空间)
    2. 函数的参数就在临时栈中。如果函数传递实参,则用来初始化临时参数变量。
    3. 通过寄存器返回值(使用返回值返回数据)
    4. 通过参数返回值(参数必须是指针。指针指向的区域必须事先分配)
    5. 如果参数返回指针,参数就是双指针。

    5.2 __stdcall, __cdecl __fastcall的问题(了解,应付面试即可)

    #include <stdio.h>
    int _attribute_((stdcall)) add(int *a, int *b)
    {
        return *a+*b;
    }
    
    int main()
    {
        int a1 = 20;
        int b2 = 30;
        int r = add(&a, &b);
        
        printf("%d\n", r);
    }
    1. 这三个属性决定函数参数压栈顺序。都是从右到左。
    2. 决定函数栈清空的方式。是调用者清空还是被调用者清空
    3. 决定了函数的名字转换方式。(编译的时候,会把函数重新命名。)

    6. far near huge指针的问题(linux中不考虑这个问题。window中属于遗留问题。windows编程统一采用far指针)

        near  16

        far    32

        huge 综合

      Note: C与C++明显的不同表现在 引用, 模板, 异常以及面向对象

                  函数参数传值和传指针其实是一样的,只是一个是把值拷贝过去,一个是把地址拷贝过去。

    虚拟内存

    小实验:写一个程序,定义一个整型指针,赋值为999,打印出它的地址,同时while(1)让它一直运行着。再写一个程序,定义一个整形指针,直接指向刚才打印出来的地址,然后打印这个指针指向的整数,为打印出999吗?  (不会,段错误)

    问题:

    为什么一个程序不能访问另外一个程序的地址指向的空间?

    理解:

    1. 每个程序的开始地址一般都是0x80084000。
    2. 由1可以看出程序中使用的地址不是物理地址,而是逻辑地址(虚拟内存)。逻辑地址仅仅是个编号,使用int 4字节整数表示。(4字节所能表示的最大整数是2的32次方=4294967296=4G)。所以每个程序提供了4G的访问能力

    问题:

           逻辑地址和物理地址怎么关联?(内存映射)

    背景:

           虚拟内存的提出:禁止用户直接访问物理存储地址。有助于系统的稳定。

    结论:

           虚拟地址与物理地址在映射的时候有一个基本单位4k(16进制的1000,称为内存页)。

           段错误:无效访问。虚拟地址与物理地址没有映射。

           没有段错误不一定是合法访问。

           合法访问:比如malloc分配的空间之外的空间(malloc后就映射了)可以访问但是访问非法。int *p1 = malloc(4);   int *(p1+12) = 233;  第二句不会报段错误,但是是非法访问。

    虚拟内存的分配

           栈:编译器自动生成代码维护

           堆:地址是否映射?映射的空间是否被管理?

    1. brk/sbrk内存映射函数

      补充:帮助文档:man 节 关键字 

      节:1-8    1: Linux系统(shell)指令  (ls等)

                    2: 系统函数  (brk等)

                    3: 标准C函数的帮助文档  (fopen等)

                    7: 系统的编程帮助  (tcp, icmp等)

           分配释放内存

           int brk(void *addr);  //分配空间,释放空间

           void *sbrk(int size);  //返回指定大小的空间的地址

           应用:

    1. 使用sbrk分配内存空间    int *p = sbrk(4);  //分配4字节整数
    2. 使用sbrk得到没有映射的虚拟地址  int *p1 = sbrk(0);  //返回没有映射的虚拟地址的首地址,不能给*p1赋值,会出现段错误。因为还没有映射。
    3. 使用brk分配空间
    4. 使用brk释放空间

    理解:

           sbrk(int size)

           sbrk与brk后台系统维护一个指针。指针默认是null。

           调用sbrk,判定指针是否是0(第一次调用),如果是:得到大块空闲地址的首地址来初始化该指针。返回该指针给指针变量赋值,同时把指针指向+size的地方。如果是否:返回指针,并且将指针位置+size。

    #include <stdio.h>
    #include <unistd.h>
    
    int main()
    {
        int *p = sbrk(0);  //返回空闲地址,并修改指针为+size(这里是0,),注意这个指针不是*p,而是sbrk指向内存里的指针。
    //这里是0,并且是首次调用,所以内存并没有映射。如果括号里是4或者4的倍数,则会返回指针的同时做映射,并把sbrk的指针指向+4的位
    //置以便供下一次调用的时候返回地址。并不是括号里是4就只映射4个字节的地址,而是映射一页的内存。这是为了效率的考虑。好比吃馒头,
    //不是吃一个做一个,而是要吃了,做一屉,慢慢吃。所以 *(p+10)= 20; 是可以访问的(p最多只能加到1023,不然仍然会段错误)。
    //但是是非法访问。
    printf("%d\n", *p); }
    #include <stdio.h>
    #include <unistd.h>
    
    int main()
    {
        int *p1 = sbrk(4);  //返回空闲地址,并修改指针为+size
        int *p2 = sbrk(0);
        
        printf("%p\n", p1);
        printf("%p\n", p2);  //通过上面的程序分析,这里打印的
    //是p1加上4个字节后的地址。int *p2 = sbrk(200);这句话括号里即使
    //是200,p2也是p1加4个字节,因为sbrk是先返回当前的地址,再加括
    //号里的size。如果括号里是负数,则表示释放空间。
        
        while(1);
    }


    下面再看brk(void *p)函数:

    #include <stdio.h>
    #include <unistd.h>
    
    int main()
    {
        int *p = sbrk(0);
        brk(p+1);  //将sbrk里面的指针向后移动4个字节,发现没
    //有映射,就会自动映射区域。所以后面的*p就可以访问了。
        *p = 800;
        brk(p);   //将指针再移回去,相当于释放内存空间,即取
    //消之前的映射。后面再访问就会出错了。
        *p = 29;  //段错误。
        
        while(1);
    }


    应用案例:

           写一个程序查找1-10000之间的所有的素数,并且存放到缓冲,然后打印。

           分析:1-10000如果用数组的话,不太现实,有大部分空间都用不上。C++的话可以用链表实现,但是链表的开销比较大。用malloc和new都不太好。所以,缓冲的实现使用sbrk/brk。

           流程:

                  判断是否是素数(isPrime)

                  是,分配空间存放

                  否,继续下步

    #include <stdio.h>
    #include <unistd.h>
    
    int isPrime(int a)
    {
        int i = 0;
        for(i = 2; i < a; i++)
        {
            if(a%i == 0)
                {
                    return 1;
                }
        }
        return 0;
    }
    
    int main()
    {
        int i = 2; //循环变量
        int *r;
        int *p;  //p一直指向页首
        r = sbrk(0);
        p = r;
        for(; i<10000; i++)
        {
            if(isPrime(i))
                {
                    brk(r+1);
                    *r = i;
                    r = sbrk(0);
                }
        }
        
        i = 0;
        r = p;
        while(r != sbrk(0))
        {
            printf("%d\n", *r);
            r++;
        }
        brk(p);  //释放空间
    }

    总结:

           new   //C++里面用得比较多

           malloc  //C里面用得比较多,一定要制定空间大小

           brk/sbrk  //数据比较简单,量比较大的时候用效率比较高

    异常处理

           int brk(void *)  //返回int值

           void *sbrk(int)  //返回指针

           如果成功,brk返回0, sbrk返回指针

           如果失败, brk返回-1, sbrk返回(void *)-1

    #include <string.h>
    #include <errno.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <stdio.h>
    
    int main()
    {
        void *p = sbrk(1000000000*3);
        if(p == (void *)-1)
            {
                printf("error!");
                perror("Hello"); //打印出错误信息
                printf("%m");  //打印出memory error
                printf("%s", strerror(errno));
            }
    }


    以下是一些比较常用的函数:

           字符串函数string.h      cstring

           内存管理函数malloc    memset   mamcmp memcpy…bzero

           错误处理函数

           时间函数

           类型转换函数

    作业:

           找出打印1-10000之间的所有孪生素数。

  • 相关阅读:
    HDU 5338(ZZX and Permutations-用线段树贪心)
    编程之美-活动中心(三分)
    form的method用get导致中文乱码
    Tomcat: Could not clean server of obsolete files
    Eclipse打开javadoc框
    Java EE各种documentation
    web-project的/WEB-INF/lib
    在jsp里面不要瞎用<!-- -->注释
    [流水账]搜索与web-container版本匹配的jar包
    session的创建与销毁
  • 原文地址:https://www.cnblogs.com/tangzhengyue/p/2576742.html
Copyright © 2020-2023  润新知