c 语言中打印指针的值打印的是 OS 分配的虚拟地址的值吗?要怎么知道 OS 给这个 c 程序进程分配的虚拟地址的大小呢?

50 天前
 rookiemaster

并且如何知道哪一块内存大小是可以读可以写,以避免出现下面程序的 Segmentation Fault 呢?

#include<stdio.h>
int main(){
	int *p = (int *)0x1;
    printf("%p\n", p);
    *p = 1;           // segmentation fault
    printf("%d", *p); // segmentation fault
}

1518 次点击
所在节点    C
17 条回复
iOCZS
50 天前
是虚拟的,每个进程都有自己的虚拟地址空间,范围从 0x000'0000000 到 0x7FF'FFFFFFFF 。
内存里放着很多的段,像代码段被设置为只读的话,你去写入就会段错误。
proxytoworld
50 天前
有 api 读取内存的属性
llh880808
50 天前
1. c 语言中打印指针的值打印的是 OS 分配的虚拟地址的值吗?
是的,应用程序看到的地址都是虚拟地址
2. 要怎么知道 OS 给这个 c 程序进程分配的虚拟地址的大小呢?
每一个应用都会分配到整个物理内存大小的虚拟内存
3. 如何知道哪一块内存大小是可以读可以写?
使用更高特权等级(默认的 user 等级应该是无权访问的),查询 PMP 相关配置可以拿到内存地址的读写权限
BeiChuanAlex
50 天前
你有没有想过把这个转成汇编,汇编基本上一目了然
yanqiyu
50 天前
@llh880808 #3
> 每一个应用都会分配到整个物理内存大小的虚拟内存
并不准确,分配的空间大小和物理内存大小没关系。精简点的程序的用户地址空间甚至可能就只有程序和动态库的映射+自己的栈,花哨点的可以上来就要 TB 量级的匿名 mmap 自己分配。

> 使用更高特权等级(默认的 user 等级应该是无权访问的),查询 PMP 相关配置可以拿到内存地址的读写权限
其实可以读自己的 maps 文件/smaps 文件来查询(怎么看怎么不清真)
或者想办法直接干了然后捕获 SIGSEGV 就知道是不是可读可写了...
yanqiyu
50 天前
@BeiChuanAlex 这和汇编没关系,虚拟地址分配,虚拟地址到物理地址的映射和 segmentation fault 都发生在 mmu 和 OS 内部,对你的程序是透明的
gcl123
50 天前
进入 x86 实模式,就能打出实际内存地址了
chouxw112233
50 天前
1. 虚拟地址
2. 不知道,否则函数传递数组,被退化指针,也不需要额外再传一个 size 了
3. 不是你申请出来的,就是不可以读写的,即使读写不出现 segfault ,也会很危险
leonshaw
50 天前
linux 的话读 procfs 里的 maps
dangyuluo
49 天前
可以通过读某个寄存器的值再进行对比来获得 stack 已用的大小,至于 heap 的大小的话忘了,用`brk`和`sbrk`的值来判断?
geelaw
49 天前
> C 语言中打印指针的值打印的是 OS 分配的虚拟地址的值吗?

不是,它打印的是这个指针的整数表示。没有要求指针的整数表示必须等于操作系统级别的虚拟地址。当然,最常见的实现里,指针的整数表示就是它指向的对象的内存位置的虚拟地址。

> 要怎么知道 OS 给这个 C 程序进程分配的虚拟地址的大小呢?

操作系统不给“C 程序”分配虚拟地址,“虚拟地址”的大小可能也不是你想要的那个答案。操作系统为每个进程提供虚拟内存,每个程序都以为自己占有全部寻址空间的虚拟内存,虚拟地址的大小和处理器有关。

有意义的问题是:一个进程如何知道自己的虚拟内存里有多少页是可读的、可写的、可执行的?这个问题无法从 C 语言的抽象层级回答,你需要操作系统的 API 。

> 并且如何知道哪一块内存大小是可以读可以写,以避免出现下面程序的 Segmentation Fault 呢?

在 C 语言看来,只有 malloc 等分配得到且未 free 、realloc 等的、取静态存储期对象的地址得到的、取自动存储期对象地址得到且该对象还未离开作用域的指针,以及它进行有定义算术运算得到的指针,才是有效的指针。解引用无效指针的效果是未定义行为,段错误仅仅是它的一种表现方式。

当然,结合操作系统 API 之后有更多获得有效指针的方式。但楼主本来的这个问题意义不明——如果你不知道一个指针怎么来的,使用它有什么意义呢?如果你知道一个指针怎么来的,那你当然知道这个指针是否有效。
MeePawn666
49 天前
搜索 MMU ,你就都明白了
dhb233
49 天前
如果是要好好写程序,指针就不应该随便乱用啊。。。指针只应该指向你知道大小的内存,并且是在编码阶段就能明确内存大小的内存。比如 malloc 的一块内存,你申请的时候是知道有多大的;栈上的一个变量,你应该知道变量的 sizeof ;一个给定长度的数组,最大就是数组的 size ;无论哪种,你在写代码的时候,都要知道这块内存有多大。
同样的,传递指针的时候,要么是一个结构体的指针,要么一定加一个长度参数。

你非要用一些骚操作来解决,可以直接捕获段错误的信号啊,没有段错误就是没越界。但是这个也仅代表你没有越分配内存的界。如果越界写到了其他数据结构,那还是没办法。
sbldehanhan
49 天前
你需要内存就向操作系统申请一片内存,它会告诉你可用的内存地址,使用完就把它释放掉,至于哪片内存可用,哪片不可用,那是操作系统的事。如果这件事由程序员自己做,那要操作系统干啥?再说,你管理的过来吗?到时候程序不得走一步崩两次?
4king
49 天前
pmap 可以看进程内存分布,得到虚拟地址对照看就行
heguangyu5
49 天前
过一遍一个可执行文件(ELF)是怎么被操作系统(linux kernel)加载并执行的,就很清楚了.

http://heguangyu5.github.io/my-linux/html/20-init_post.html
PTLin
49 天前
给你解释一下为什么你的代码会发生 segmentation fault ,以加强你对整个体系的认识。

在 Linux 中,地址空间会被分成一系列的段,例如映射到可执行文件段,映射到共享库的段,匿名映射(通常被用于堆)的段,这一系列段由叫 vma 结构的集合组成。可以从 proc 文件系统对应进程号的 maps 文件看到。

对于 x86 来讲虚拟地址会通过页表进行地址映射。倘若在页表里地址对应的条目不存在将会引发 page_fault 中断。
在中断的处理过程内,由于你的地址 0x1 是用户地址所以跳转到了处理用户地址的函数 do_user_addr_fault 。

这个函数会查找这个地址是否属于某个 vma ,然而没有查询到,所以调用了 bad_area_nosemaphore 向这个进程发送了 SIGSEGV 信号。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1025538

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX