c 语言中打印指针的值打印的是 OS 分配的虚拟地址的值吗?要怎么知道 OS 给这个 c 程序进程分配的虚拟地址的大小呢? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
rookiemaster
V2EX    C

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

  •  
  •   rookiemaster 2024-03-20 20:33:52 +08:00 3064 次点击
    这是一个创建于 639 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    #include<stdio.h> int main(){ int *p = (int *)0x1; printf("%p\n", p); *p = 1; // segmentation fault printf("%d", *p); // segmentation fault } 
    17 条回复    2024-03-21 10:55:32 +08:00
    iOCZS
        1
    iOCZS  
       2024-03-20 20:40:23 +08:00
    是虚拟的,每个进程都有自己的虚拟地址空间,范围从 0x000'0000000 到 0x7FF'FFFFFFFF 。
    内存里放着很多的段,像代码段被设置为只读的话,你去写入就会段错误。
    proxytoworld
        2
    proxytoworld  
       2024-03-20 20:44:52 +08:00
    有 api 读取内存的属性
    llh880808
        3
    llh880808  
       2024-03-20 20:53:29 +08:00
    1. c 语言中打印指针的值打印的是 OS 分配的虚拟地址的值吗?
    是的,应用程序看到的地址都是虚拟地址
    2. 要怎么知道 OS 给这个 c 程序进程分配的虚拟地址的大小呢?
    每一个应用都会分配到整个物理内存大小的虚拟内存
    3. 如何知道哪一块内存大小是可以读可以写?
    使用更高特权等级(默认的 user 等级应该是无权访问的),查询 PMP 相关配置可以拿到内存地址的读写权限
    BeiChuanAlex
        4
    BeiChuanAlex  
       2024-03-20 22:48:50 +08:00
    你有没有想过把这个转成汇编,汇编基本上一目了然
    yanqiyu
        5
    yanqiyu  
       2024-03-20 22:57:31 +08:00   1
    @llh880808 #3
    > 每一个应用都会分配到整个物理内存大小的虚拟内存
    并不准确,分配的空间大小和物理内存大小没关系。精简点的程序的用户地址空间甚至可能就只有程序和动态库的映射+自己的栈,花哨点的可以上来就要 TB 量级的匿名 mmap 自己分配。

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

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

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

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

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

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

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

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

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

    http://heguangyu5.github.io/my-linux/html/20-init_post.html
    PTLin
        17
    PTLin  
       2024-03-21 10:55:32 +08:00
    给你解释一下为什么你的代码会发生 segmentation fault ,以加强你对整个体系的认识。

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

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

    这个函数会查找这个地址是否属于某个 vma ,然而没有查询到,所以调用了 bad_area_nosemaphore 向这个进程发送了 SIGSEGV 信号。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     904 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 29ms UTC 19:44 PVG 03:44 LAX 11:44 JFK 14:44
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86