【问题标题】:The bounds on void-pointers in ANSI C89/ISO C90ANSI C89/ISO C90 中空指针的界限
【发布时间】:2009-01-22 18:40:11
【问题描述】:

有没有办法在 ANSI C89/ISO C90 中可移植地确定 void-pointer 值的上限和下限?(我目前没有标准的副本(我有一个在家里)。当然,如果 void-pointer 值保证是无符号的,这个任务是微不足道的(通过 sizeof(void *));但是,我不记得这是否得到保证。我能想到一些非常低效的算法(递增直到溢出等),但我想知道是否有人有相对便宜(就时间复杂性而言)和便携的方法来计算这些界限。)

--编辑--

另外:是否有可移植的方法来确定指针值的有效性?

原因:这是在与一位同事的讨论中提出的,这让我很困惑。我不知道他在做什么,但我只是想知道,因为我有兴趣! :-)

【问题讨论】:

  • 为什么你需要知道这个?如果您尝试确定给定的指针是否指向有效的内存位置,那么几乎可以肯定有更好的方法来执行您想要执行的操作。

标签: c void-pointers c89


【解决方案1】:

没有可移植的方法来确定给定指针是否有效。你必须知道你正在处理什么样的记忆系统。根据操作系统和处理器,可能有也可能没有方法来查询虚拟内存管理器的页表以确定指针的有效范围。

例如,在 Linux 上,您可以检查 /proc 下的特殊 mmap 文件以获取进程的虚拟内存映射。下面是cat 读取自己的内存映射的例子:

$猫/proc/self/mmap 08048000-0804c000 r-xp 00000000 09:00 5128276 /bin/cat 0804c000-0804d000 rw-p 00003000 09:00 5128276 /bin/cat 0804d000-0806e000 rw-p 0804d000 00:00 0 [堆] f7ca7000-f7e40000 r--p 00000000 09:00 3409654 /usr/lib/locale/locale-archive f7e40000-f7e41000 rw-p f7e40000 00:00 0 f7e41000-f7f68000 r-xp 00000000 09:00 2654292 /lib/tls/i686/cmov/libc-2.3.6.so f7f68000-f7f6d000 r--p 00127000 09:00 2654292 /lib/tls/i686/cmov/libc-2.3.6.so f7f6d000-f7f6f000 rw-p 0012c000 09:00 2654292 /lib/tls/i686/cmov/libc-2.3.6.so f7f6f000-f7f72000 rw-p f7f6f000 00:00 0 f7f83000-f7f85000 rw-p f7f83000 00:00 0 f7f85000-f7f9a000 r-xp 00000000 09:00 2637871 /lib/ld-2.3.6.so f7f9a000-f7f9c000 rw-p 00014000 09:00 2637871 /lib/ld-2.3.6.so ff821000-ff836000 rw-p 7ffffffea000 00:00 0 [堆栈] ffffe000-fffff000 r-xp ffffe000 00:00 0 [vdso]

您可以看到有效指针的范围,以及指示内存是否 (r) 可读、 (w) 可写、 e(x) 可执行或 (p) 可重新发送(即未分页到磁盘)的位.

【讨论】:

    【解决方案2】:

    规范保证指针是无符号的。但是你到底为什么要找到界限呢? “0x00000001 和 0xffffffff 之间的所有内容”并不是一个真正有用的测试,因为 valid 指针的数量只是其中的一小部分。

    【讨论】:

    • 该范围仅在 32 位机器上有效。
    • 此外,所有0 可能是某些机器上指针的有效位模式:该标准允许在转换((void *)0) 时发生转换 - 空指针的实际位模式未指定!
    • 什么规范保证指针是无符号的? C 语言标准没有这样的保证。指针不是整数;他们没有签名。
    【解决方案3】:

    void * 总是大到足以容纳指向可寻址内存的指针。美国职业棒球大联盟严禁任何其他用途。

    示例:dec-10 是具有 36 位字的 36 位架构。然而地址是 18 位,你可以在任何寄存器/字中保存 2 个指针。

    是的 - 这是一个极端的例子。如果你必须用指针做数学运算,sizeof 是有效的;但是对连续数组以外的任何东西进行指针数学运算都比不可靠。

    最后 - 永远不要使用 'void *' 来存储指向对象的指针或指向 C++ 中成员的指针。许多编译器实现实际上使用多个“物理”指针来实现具体(或部分具体)类的多重继承。实际上,这几乎从来没有出现过,因为很少有人以这种方式使用多重继承,而且当他们这样做时,很少会分片和取消分片指针。当它真的出现时,真的很难弄清楚发生了什么。

    【讨论】:

    • 您能否提及一些具有这种“胖”对象指针的编译器?
    • 当然。 Visual C 是肯定的。我没有反汇编 gcc 看看它是否做同样的事情,但我不确定它是怎么做不到的。如果您有多个具体继承并在多个基类的虚拟上创建指向成员的指针 - 我不确定在通过继承的最终类通过基类使用时如何正确引用它们。
    【解决方案4】:

    您必须从内存中的实际位模式中辨别void * 可以转换为的整数值 - 将void * 转换为整数类型可能涉及转换!

    假设sizeof(void *) == sizeof(long),对于void * p,以下很可能是错误的:

    ((long)p) == *((long *)&p)
    

    此外,标准也没有指定是否存在 一个足够大的整数类型来容纳所有有效指针的值!

    因此,没有可移植的方式来做你想做的事......

    【讨论】:

    • 没错,尽管在 C99 中,您可以使用 intptr_t 和 uintptr_t 类型,它们分别是有符号和无符号整数类型,它们保证足够大以容纳指针。
    • @Adam:只有一个小问题(C99-TC3,7.18.1.4):“这些类型是可选的。”
    【解决方案5】:

    除了对应于 NULL 的区域之外,对内存地址根本没有(可移植的)限制。一个充分强化的操作系统可以利用各种 CPU/OS 机制在每次调用 malloc() 时为每个进程提供随机且分布良好的地址,并且位置独立的可执行文件加上 ASLR 也可以允许代码从任何地址运行。

    【讨论】:

      【解决方案6】:

      我知道在 Win32 上,64 位指针是符号扩展的。如果不签署扩展指针,从 64 位机器检查 32 位小型转储会很有趣。

      请参阅here,了解 64 位指针 (POINTER_64) 在 Win32 上的工作原理。

      【讨论】:

      • 符号扩展指针可能不好。如果 32 位“LARGEADDRESSAWARE”进程将用户地址传递给内核,并且内核符号在将其转换为 64 位指针时对其进行了扩展,则它现在指向内核地址空间,并且 ProbeForRead/Write 将拒绝它。
      • win32 中的指针是无符号的。如果设置了 /3GB 标志并且进程是大地址感知的,则用户模式地址可以设置第一位。你不应该假设指针是用 Win32 签名的。
      • 不,我是对的。至少在处理来自 64 位机器的 32 位指针时:msdn.microsoft.com/en-us/library/aa384264(VS.85).aspx
      猜你喜欢
      • 1970-01-01
      • 2020-07-22
      • 2017-05-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-08
      相关资源
      最近更新 更多