UNIXC01 内存管理02

获取进程的PID

  • 只有知道了进程的pid以后, 才能获取到进程映像的相关信息
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
  • 功能: 获取当前进程的pid

  • 返回值: 返回当前进程的pid

  • 补充:

    • cat /proc/$$/maps #查看当前bash进程的映像文件获取进程的相关信息
    • cat /proc/$进程pid/maps #获取指定进程的相关信息

代码示例

get_pid.c

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void){
    printf("pid: %d\n", getpid());
    getchar();
    return 0;
}
# 通过预处理后得到的 get_pid.i 可以看到 getpid() 返回值类型pit_t 是整型
$ gcc -E get_pid.c -O get_pid.i
$ cat get_pid.i
....
typedef int __pid_t;
....
typedef __pid_t pid_t; #所以getpid()的返回值是整型
....
$ gcc get_pid.c
$ a.out
pid: 21692

# 在等待输出字符串的时候(也就是这个进程还没结束的时候) 去查看进程信息
$ cat /proc/21692/maps
00400000-00401000 r-xp 00000000 103:02 21637778                          /home/moonx/Desktop/project_xuehui/C_learnning/uc_memory/a.out	# 代码段
00600000-00601000 r--p 00000000 103:02 21637778                          /home/moonx/Desktop/project_xuehui/C_learnning/uc_memory/a.out	# 只读数据段
00601000-00602000 rw-p 00001000 103:02 21637778                          /home/moonx/Desktop/project_xuehui/C_learnning/uc_memory/a.out	# 数据段
01db8000-01dd9000 rw-p 00000000 00:00 0                                  [heap] #堆
7f0d26e34000-7f0d26ff4000 r-xp 00000000 103:02 26481522                  /lib/x86_64-linux-gnu/libc-2.23.so	# 依赖的动态库
7f0d26ff4000-7f0d271f4000 ---p 001c0000 103:02 26481522                  /lib/x86_64-linux-gnu/libc-2.23.so
7f0d271f4000-7f0d271f8000 r--p 001c0000 103:02 26481522                  /lib/x86_64-linux-gnu/libc-2.23.so
7f0d271f8000-7f0d271fa000 rw-p 001c4000 103:02 26481522                  /lib/x86_64-linux-gnu/libc-2.23.so
7f0d271fa000-7f0d271fe000 rw-p 00000000 00:00 0 
7f0d271fe000-7f0d27224000 r-xp 00000000 103:02 26485177                  /lib/x86_64-linux-gnu/ld-2.23.so
7f0d273d9000-7f0d273dc000 rw-p 00000000 00:00 0 
7f0d27423000-7f0d27424000 r--p 00025000 103:02 26485177                  /lib/x86_64-linux-gnu/ld-2.23.so
7f0d27424000-7f0d27425000 rw-p 00026000 103:02 26485177                  /lib/x86_64-linux-gnu/ld-2.23.so
7f0d27425000-7f0d27426000 rw-p 00000000 00:00 0 
7fff52a24000-7fff52a46000 rw-p 00000000 00:00 0                          [stack]	#栈段
7fff52ab3000-7fff52ab6000 r--p 00000000 00:00 0                          [vvar]
7fff52ab6000-7fff52ab8000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

# 程序中是分着各个段的, C中是看不到分段操作的, 但可以在汇编中看到
$ gcc -S get_pid.c -o get_pis.s
$ cat get_pis.s
	.file	"get_pid.c"
	.section	.rodata	 # .section代表区间, 这个区间是 .rodata 只读区间
.LC0:
	.string	"pid: %d\n"
	.text	# 代表代码区间, 前面省略了.section
	....
	.section	.note.GNU-stack,"",@progbits	# 栈区间

段的形成

区间链接生成段
在这里插入图片描述

  • 多个目标文件通过链接器链接生成可执行文件
  • 蓝色是代码区间,绿色是数据区间, 红色是栈区间
  • 链接时,把多个目标文件中相同的区间section,合并到a.out中形成segment

栈段和代码段

代码示例

在这里插入图片描述
str1 和 str2 两个指针变量都在栈段里, 但是它俩的内容(指针)都指向代码段的"hello beijing"的地址, 所以它俩的内容是一样的,
memory.c

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(void){
    printf("pid: %d\n", getpid());

    // str1 str2 是两个指针变量, 都在栈段里, 有各自的地址&str1,&str1, 同时这两个变量的内容也是地址指向代码段里"hello beijing"的地址
    // 所以这两个变量的内容是一样, 
    char *str1 = "hello beijing";
    char *str2 = "hello beijing";

    //下面两个语句干了两件事,
    // 1.在栈段里分配了32字节的空间给buf
    // 2.把代码段的"hello beijing"拷贝了一份给到栈段的buf里
    // 相当于??? char buf[32] = "hello beijing";
    char buf[32]; 
    strcpy(buf, "hello beijing");
    // char buf[32]; buf = "hello beijing"; 这样写是不行的, 这相当于在buf地址确定以后, 修改buf的地址, buf地址是常量,不能被修改, 这是一个错误的操作
    // 但是 char *str3 = NULL; str3 = "hello beijing";  这样是可以的, 因为没有改变指针变量的地址, 改的只是指针变量的内容;
    
    printf("&str1=%p\n", &str1); //这是栈段中str1变量的地址
    printf("&str2=%p\n", &str2); //这是栈段中str2变量的地址

    printf("str1=%p\n", str1);  //这是栈段中str1变量的内容, 内容是指向代码段的"hello beijing"的地址
    printf("str2=%p\n", str2);  //这是栈段中str2变量的内容, 内容是指向代码段的"hello beijing"的地址

    printf("buf=%p\n", buf);    //这是栈段中数组的名字(首地址), 也就是栈段中"hello beijing"的首地址
    printf("buf: %s\n", buf);   //这是地址中存储的字符串
    
    
    getchar();
    return 0;
}
$ ./a.out 
pid: 9453
&str1=0x7ffcde935330
&str2=0x7ffcde935338
str1=0x4007cd
str2=0x4007cd
buf=0x7ffcde935340
buf: hello beijing


$ cat /proc/9453/maps
00400000-00401000 r-xp 00000000 08:00 109318701                          /media/moonx/89502b39-e263-4240-a2cc-df08a8909996/c++_learning/mem_manage/a.out
00600000-00601000 r--p 00000000 08:00 109318701                          /media/moonx/89502b39-e263-4240-a2cc-df08a8909996/c++_learning/mem_manage/a.out
00601000-00602000 rw-p 00001000 08:00 109318701                          /media/moonx/89502b39-e263-4240-a2cc-df08a8909996/c++_learning/mem_manage/a.out
00ffc000-0101d000 rw-p 00000000 00:00 0                                  [heap]
7fcde847a000-7fcde863a000 r-xp 00000000 08:12 12189779                   /lib/x86_64-linux-gnu/libc-2.23.so
7fcde863a000-7fcde883a000 ---p 001c0000 08:12 12189779                   /lib/x86_64-linux-gnu/libc-2.23.so
7fcde883a000-7fcde883e000 r--p 001c0000 08:12 12189779                   /lib/x86_64-linux-gnu/libc-2.23.so
7fcde883e000-7fcde8840000 rw-p 001c4000 08:12 12189779                   /lib/x86_64-linux-gnu/libc-2.23.so
7fcde8840000-7fcde8844000 rw-p 00000000 00:00 0 
7fcde8844000-7fcde886a000 r-xp 00000000 08:12 12189790                   /lib/x86_64-linux-gnu/ld-2.23.so
7fcde8a2b000-7fcde8a2e000 rw-p 00000000 00:00 0 
7fcde8a69000-7fcde8a6a000 r--p 00025000 08:12 12189790                   /lib/x86_64-linux-gnu/ld-2.23.so
7fcde8a6a000-7fcde8a6b000 rw-p 00026000 08:12 12189790                   /lib/x86_64-linux-gnu/ld-2.23.so
7fcde8a6b000-7fcde8a6c000 rw-p 00000000 00:00 0 
7ffcde916000-7ffcde938000 rw-p 00000000 00:00 0                          [stack]
7ffcde9c9000-7ffcde9cc000 r--p 00000000 00:00 0                          [vvar]
7ffcde9cc000-7ffcde9ce000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

栈段,栈帧,数据段

在这里插入图片描述

  • main函数调用func_1, func_1调用func_2, func_2调用func_3
    • M点是main函数调用func_1的点, 执行到M之前的数据需要保存在内存中,(不然程序跳到func_1时之前的数据会被破坏掉)
    • M点的数据会被保存到一块内存的黑色部分, SP寄存器指向M, func_1可使用SP下方的内存(func_1的栈帧), 上面不可用, 不然会破坏main函数保存的数据,
    • 然后再从func_1 跳到 func_2, func_1的数据保存到那块内存的绿色部分F1, SP寄存器指向F1, func_2 只能使用F1下方的内存(func_2的栈帧)
    • func2跳到func_3时, func2的数据保存在内存中的蓝色部分, SP指向F2如上图所示, func_3可使用F2下面内存(func3的栈帧)
    • 在func3执行完毕以后, 这块内存就会被释放出来(func3的栈帧被释放), 然后跳回func_2, SP依然指向F2, func_2使用的内存就变成蓝色加下面白色.(func2的栈帧)
    • func_2执行完毕以后,蓝色内存释放, 跳回func_1, SP指向F1. func_1可使用的内存就变成绿色加下面所有(func_1的栈帧).
    • func_1执行完毕以后, 跳回main, SP指向M, main可以使用的内存就变成黑色加下面所有(main的栈帧)
  • 在这块内存中, M先进, 然后是F1, F2, 释放是F2先释放, 遵循先进后出, 这块内存就是所谓的栈段
  • 栈段里包含很多个栈帧, 每个函数执行的时候都有自己的栈帧, 执行完毕以后,函数释放对应栈帧
  • 局部变量 和 形参会分配在栈帧里

栈帧和栈段的使用

  • 局部变量 和 形参分配在函数对应的栈帧里
    cout.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

void cout(void){
    // 第一次调用cout时, 就有了cout函数对应的栈帧
    // 然后为val分配内存空间, 用1来初始化这个内存空间, 所以val会被分配在栈帧里
    int val = 1;
    printf("val++=%d\n", val++);
    //val会分配在栈帧里面
    printf("&val=%p\n", &val);
    // 返回以后, 释放栈帧, val在栈帧空间里, val也就被释放了
    // 然后第二次调用时, 重复上面的过程, 所以每次输出的都是1
    return;
}

int main(void){
    printf("pid: %d\n", getpid());
    int i;
    for (i=0; i<5; i++){
        cout();
    }
    getchar();
    return 0;
}
$ gcc cout.c 
$ ./a.out 
pid: 5814
val++=1
&val=0x7ffc3f6445e4
val++=1
&val=0x7ffc3f6445e4
val++=1
&val=0x7ffc3f6445e4
val++=1
&val=0x7ffc3f6445e4
val++=1
&val=0x7ffc3f6445e4

$ $ nm a.out | grep val #返回的是空, 说明是程序执行以后才给val分配的地址
$ cat /proc/5814/maps
00400000-00401000 r-xp 00000000 08:00 109318701                          /media/moonx/89502b39-e263-4240-a2cc-df08a8909996/c++_learning/mem_manage/a.out
00600000-00601000 r--p 00000000 08:00 109318701                          /media/moonx/89502b39-e263-4240-a2cc-df08a8909996/c++_learning/mem_manage/a.out
00601000-00602000 rw-p 00001000 08:00 109318701                          /media/moonx/89502b39-e263-4240-a2cc-df08a8909996/c++_learning/mem_manage/a.out
01996000-019b7000 rw-p 00000000 00:00 0                                  [heap]
7f745d409000-7f745d5c9000 r-xp 00000000 08:12 12189779                   /lib/x86_64-linux-gnu/libc-2.23.so
7f745d5c9000-7f745d7c9000 ---p 001c0000 08:12 12189779                   /lib/x86_64-linux-gnu/libc-2.23.so
7f745d7c9000-7f745d7cd000 r--p 001c0000 08:12 12189779                   /lib/x86_64-linux-gnu/libc-2.23.so
7f745d7cd000-7f745d7cf000 rw-p 001c4000 08:12 12189779                   /lib/x86_64-linux-gnu/libc-2.23.so
7f745d7cf000-7f745d7d3000 rw-p 00000000 00:00 0 
7f745d7d3000-7f745d7f9000 r-xp 00000000 08:12 12189790                   /lib/x86_64-linux-gnu/ld-2.23.so
7f745d9ba000-7f745d9bd000 rw-p 00000000 00:00 0 
7f745d9f8000-7f745d9f9000 r--p 00025000 08:12 12189790                   /lib/x86_64-linux-gnu/ld-2.23.so
7f745d9f9000-7f745d9fa000 rw-p 00026000 08:12 12189790                   /lib/x86_64-linux-gnu/ld-2.23.so
7f745d9fa000-7f745d9fb000 rw-p 00000000 00:00 0 
7ffc3f625000-7ffc3f647000 rw-p 00000000 00:00 0                          [stack]
7ffc3f73b000-7ffc3f73e000 r--p 00000000 00:00 0                          [vvar]
7ffc3f73e000-7ffc3f740000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

  • 静态局部变量 和全局变量都分配在数据段里
    区别是作用域不同, 静态局部变量作用域只是所在的函数内, 全局变量作用局是全局.

cout.c

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

void cout(void){
    // 第一次调用cout时, 就有了cout函数对应的栈帧
    // int val 变成  static int val 以后, val就不在cout函数对应的的栈帧里面了
    // val就到了进程的数据段里面了, 数据段只有进程结束以后才释放
    static int val = 1; //第二次调用cout时 这句话就不会再执行了, 为什么?
    printf("val++=%d\n", val++);
    printf("&val=%p\n", &val);
    // 返回以后, 释放栈帧, val在数据段里, val不会被释放
    return;
}

int main(void){
    printf("pid: %d\n", getpid());
    int i;
    for (i=0; i<5; i++){
        cout();
    }
    getchar();
    return 0;
}
$ gcc cout.c 
$ ./a.out 
pid: 20015
val++=1
&val=0x601048
val++=2
&val=0x601048
val++=3
&val=0x601048
val++=4
&val=0x601048
val++=5
&val=0x601048

# val的地址在编译链接的时候就已经形成好了, 是静态的数据段
$ nm a.out |grep val 
0000000000601048 d val.3046 # 和上面的地址一样 &val=0x601048
$  cat /proc/20015/maps
00400000-00401000 r-xp 00000000 08:00 109318701                          /media/moonx/89502b39-e263-4240-a2cc-df08a8909996/c++_learning/mem_manage/a.out
00600000-00601000 r--p 00000000 08:00 109318701                          /media/moonx/89502b39-e263-4240-a2cc-df08a8909996/c++_learning/mem_manage/a.out
00601000-00602000 rw-p 00001000 08:00 109318701                          /media/moonx/89502b39-e263-4240-a2cc-df08a8909996/c++_learning/mem_manage/a.out
01fbb000-01fdc000 rw-p 00000000 00:00 0                                  [heap]
7f7e03b2f000-7f7e03cef000 r-xp 00000000 08:12 12189779                   /lib/x86_64-linux-gnu/libc-2.23.so
7f7e03cef000-7f7e03eef000 ---p 001c0000 08:12 12189779                   /lib/x86_64-linux-gnu/libc-2.23.so
7f7e03eef000-7f7e03ef3000 r--p 001c0000 08:12 12189779                   /lib/x86_64-linux-gnu/libc-2.23.so
7f7e03ef3000-7f7e03ef5000 rw-p 001c4000 08:12 12189779                   /lib/x86_64-linux-gnu/libc-2.23.so
7f7e03ef5000-7f7e03ef9000 rw-p 00000000 00:00 0 
7f7e03ef9000-7f7e03f1f000 r-xp 00000000 08:12 12189790                   /lib/x86_64-linux-gnu/ld-2.23.so
7f7e040e0000-7f7e040e3000 rw-p 00000000 00:00 0 
7f7e0411e000-7f7e0411f000 r--p 00025000 08:12 12189790                   /lib/x86_64-linux-gnu/ld-2.23.so
7f7e0411f000-7f7e04120000 rw-p 00026000 08:12 12189790                   /lib/x86_64-linux-gnu/ld-2.23.so
7f7e04120000-7f7e04121000 rw-p 00000000 00:00 0 
7ffde71a2000-7ffde71c4000 rw-p 00000000 00:00 0                          [stack]
7ffde71cf000-7ffde71d2000 r--p 00000000 00:00 0                          [vvar]
7ffde71d2000-7ffde71d4000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

总结:

  • 如果变量在在数据段里面, 那么变量的生命周期是整个进程, 如果在栈帧里面, 变量的生命周期是栈帧对应的函数.

  • 动态分配内存
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>

int main(void) {
    printf("pid: %d\n", getpid());
    char *p =NULL;
    // 动态分配一组连续的字节,回类型是void * 需要强制类型转换成 char *
    // p 相当于一个字符数组名, p应该是这组连续字节的首地址
    p = (char *)malloc(1024);
    
    if (p){
        printf("&p=%p\n", &p);  // p地址在栈段中
        printf("p=%p\n", p);    // p里的内容在堆中
        strcpy(p, "hello beijing");
        printf("before free p:%s\n", p);
        // 释放一组动态分配的字节, 但p照样指着数据段的"hello beijing"
        free(p);
        // 所以下面这个输出内容是不确定的
        // 如果这块内存空间被其他线程占用以后,输出就变了
        printf("after free p:%s\n", p);

        // 但要是给它直接赋值NULL, 那么就输出NULL了
        p = NULL;
        printf("after free=NULL p:%s\n", p);
        getchar();
        
    }
    return 0;
}


$ gcc heap.c 
$ ./a.out 
pid: 16975
&p=0x7fffb2d28e60
p=0x762420
before free p:hello beijing
after free p:hello beijing
after free=NULL p:(null)

$ cat /proc/16975/maps
00400000-00401000 r-xp 00000000 08:00 109318701                          /media/moonx/89502b39-e263-4240-a2cc-df08a8909996/c++_learning/mem_manage/a.out
00600000-00601000 r--p 00000000 08:00 109318701                          /media/moonx/89502b39-e263-4240-a2cc-df08a8909996/c++_learning/mem_manage/a.out
00601000-00602000 rw-p 00001000 08:00 109318701                          /media/moonx/89502b39-e263-4240-a2cc-df08a8909996/c++_learning/mem_manage/a.out
00762000-00783000 rw-p 00000000 00:00 0                                  [heap]
7fd63c8ed000-7fd63caad000 r-xp 00000000 08:12 12189779                   /lib/x86_64-linux-gnu/libc-2.23.so
7fd63caad000-7fd63ccad000 ---p 001c0000 08:12 12189779                   /lib/x86_64-linux-gnu/libc-2.23.so
7fd63ccad000-7fd63ccb1000 r--p 001c0000 08:12 12189779                   /lib/x86_64-linux-gnu/libc-2.23.so
7fd63ccb1000-7fd63ccb3000 rw-p 001c4000 08:12 12189779                   /lib/x86_64-linux-gnu/libc-2.23.so
7fd63ccb3000-7fd63ccb7000 rw-p 00000000 00:00 0 
7fd63ccb7000-7fd63ccdd000 r-xp 00000000 08:12 12189790                   /lib/x86_64-linux-gnu/ld-2.23.so
7fd63ce9e000-7fd63cea1000 rw-p 00000000 00:00 0 
7fd63cedc000-7fd63cedd000 r--p 00025000 08:12 12189790                   /lib/x86_64-linux-gnu/ld-2.23.so
7fd63cedd000-7fd63cede000 rw-p 00026000 08:12 12189790                   /lib/x86_64-linux-gnu/ld-2.23.so
7fd63cede000-7fd63cedf000 rw-p 00000000 00:00 0 
7fffb2d0a000-7fffb2d2c000 rw-p 00000000 00:00 0                          [stack]
7fffb2db1000-7fffb2db4000 r--p 00000000 00:00 0                          [vvar]
7fffb2db4000-7fffb2db6000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

总结:

  • 主要讲了栈段, 代码段, 数据段, 堆. 栈段里面有栈帧, 栈帧对应这函数, 函数被调用时, 栈帧存在, 函数调用结束, 栈帧失去空间.

补充

  • 不要写野指针
#include <stdio.h>
int main(void){
    //指针变量p的内容(地址)没有做初始化
    // 那p的内容应该是1和0的编码组成的一个地址, 地址是不确定的, 也就是野指针
    int *p; 
    //在有权限访问, 并且这个地址存在(虚拟地址和物理地址对应)的情况下, 直接把10放到这个地址里没有问题
    //否则段错误 Segmentation fault
    *p =10;
    return 0;
}
  • 编写一个函数实现字符串的拷贝, 不使用库函数.
# 先看看函数声明
$ man strcpy
....
char *strcpy(char *dest, const char *src);
....
#include <stdio.h>
char *t_strcpy(char *dest, const char *src){
    // 思路:一个字符一个字符的拷贝知道遇到 \0 为止
    int i;
    for (i = 0; src[i] != '\0'; i++){
        dest[i] = src[i];
        // 这里如果写成 dest++ = src[i];
        // 那么最后返回的dest是字符串最尾部地址
    }
    dest[i] = '\0';
    return dest;
}
 
int main(void){
    char buf[32];
    char *msg = "hello beijing";
    t_strcpy(buf, msg);
    printf("buf: %s\n", buf); // buf: hello beijing
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值