获取进程的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;
}