Rei*_*ica 25 c pointers memory-model undefined-behavior memory-segmentation
我最近在回答一个关于p < q
当p
和q
是指向不同对象/数组的指针时在C 中执行的未定义行为的问题。这让我想到:C ++ <
在这种情况下具有相同(未定义)的行为,但是还提供了标准库模板std::less
,该模板保证可以返回与<
可以比较指针时相同的东西,并在不能比较时返回一些一致的顺序。
C是否提供具有类似功能的东西,从而可以安全地比较任意指针(相同类型)?我尝试浏览C11标准并没有发现任何东西,但是我在C中的经验比在C ++中小得多,因此我很容易错过一些东西。
Pet*_*des 19
在具有平面内存模式的实现(基本上是所有模式)上,强制转换为uintptr_t
Just Work即可。
(但是请参阅在64位x86中应该对指针比较进行签名还是不签名?以获取有关是否应该将指针视为带符号的讨论,包括在C的UB对象之外形成指针的问题。)
但是确实存在具有非平面内存模型的系统,对它们的思考可以帮助解释当前情况,例如C ++对<
vs. 具有不同的规格std::less
。
<
指向独立对象(在C中为UB)(或至少在某些C ++版本中未指定)的on指针的部分意义是允许使用怪异的机器,包括非平面内存模型。
一个著名的示例是x86-16实模式,其中指针为segment:offset,通过形成20位线性地址(segment << 4) + offset
。相同的线性地址可以由多个不同的seg:off组合表示。
std::less
怪异ISA上的指针上的C ++ 可能需要昂贵,例如,“标准化” x86-16上的segment:offset以使offset <=15。但是,没有可移植的方法来实现这一点。 规范化uintptr_t
(或指针对象的对象表示)所需的操作是特定于实现的。
但是,即使在C ++ std::less
必须昂贵的系统上,<
也不一定如此。例如,假设一个“大”内存模型,其中一个对象适合一个段,则<
可以只比较偏移量部分,而不必理会段部分。(同一对象内的指针将具有相同的段,否则它在C中的UB。C ++ 17更改为仅“未指定”,这可能仍允许跳过规范化并仅比较偏移量。)这是假定所有指向任何部分的指针的对象始终使用相同的seg
值,而不进行标准化。这就是您期望ABI需要“大”内存模型而不是“大”内存模型的原因。(请参阅注释中的讨论)。
(例如,这种内存模型的最大对象大小可能为64kiB,但最大总地址空间要大得多,可以容纳许多这样的最大大小的对象。ISOC允许实现对对象大小的限制小于最大值(无符号)size_t
可以表示,SIZE_MAX
例如,即使在平面内存模型系统上,GNU C也会将最大对象大小限制为最大值,PTRDIFF_MAX
以便大小计算可以忽略带符号的溢出。)请参见此答案和注释中的讨论。
如果要允许对象大于段,则需要一个“巨大”的内存模型,该模型必须担心在p++
遍历数组或执行索引/指针算术时指针的偏移量部分溢出。这会导致各处代码变慢,但可能意味着这p < q
将适用于指向不同对象的指针,因为针对“巨大”内存模型的实现通常会选择始终将所有指针标准化。看到什么是近,远和巨大的指针?-某些用于x86实模式的真正C编译器确实具有针对“巨大”模型进行编译的选项,除非另有声明,否则所有指针均默认为“巨大”。
x86实模式分段并不是唯一可能的非固定内存模型,它只是一个有用的具体示例,用于说明C / C ++实现如何处理它。在现实生活中,实现通过使用far
vs. near
指针的概念扩展了ISO C ,允许程序员选择何时可以相对于某些公共数据段仅存储/传递16位偏移量部分。
但是,纯ISO C实现必须在小型内存模型(除具有16位指针的相同64kiB中的代码之外的所有内存)之间选择,或者在所有指针均为32位的情况下选择大型内存或大型内存。某些循环可以通过仅增加偏移量部分来进行优化,但是指针对象无法进行优化以使其更小。
如果您知道任何给定实现的魔术操作是什么,则可以用纯C实现。问题在于,不同的系统使用不同的寻址方式,并且任何可移植的宏都无法对详细信息进行参数设置。
也许不是:它可能涉及从特殊的段表中查找某些内容,例如x86保护模式而不是实模式,其中地址的段部分是索引,而不是左移的值。您可以在保护模式下设置部分重叠的段,并且地址的段选择器部分甚至不必与相应段基地址的顺序相同。如果GDT和/或LDT没有映射到您的进程中的可读页面中,则在x86保护模式下从seg:off指针获取线性地址可能涉及系统调用。
(当然,x86的主流OS使用平面内存模型,因此段基数始终为0(使用fs
或gs
段的线程本地存储除外),并且仅32位或64位“偏移”部分用作指针。 )
您可以手动添加用于各种特定平台的代码,例如默认情况下假定为flat,或者#ifdef
用于检测x86实模式的内容并将其拆分uintptr_t
为16位的一半,seg -= off>>4; off &= 0xf;
然后将这些部分组合回32位的数字。
JL2*_*210 17
否。我曾经尝试寻找解决方法,但找不到任何东西。
最好的选择可能是强制转换,uintptr_t
并希望编译器做正确的事,就像我最终所做的那样:
void *memmove(void *dest, const void *src, size_t len)
{
const unsigned char *s = (const unsigned char *)src;
unsigned char *d = (unsigned char *)dest;
/* The most portable this is ever going to get
* without incurring an O(n) memory penalty
*/
if((uintptr_t)dest < (uintptr_t)(void *)src)
{
...
Run Code Online (Sandbox Code Playgroud)
C是否提供具有类似功能的东西,从而可以安全地比较任意指针。
没有
首先让我们只考虑对象指针。 函数指针带来了其他一系列问题。
2个指针p1, p2
可以具有不同的编码,并且指向相同的地址,因此p1 == p2
即使memcmp(&p1, &p2, sizeof p1)
不为0也是如此。此类架构很少见。
这些指针到的转换uintptr_t
不需要相同的整数结果导致(uintptr_t)p1 != (uinptr_t)p2
。
(uintptr_t)p1 < (uinptr_t)p2
本身就是很好的法律法规,可能无法提供所希望的功能。
如果代码确实需要比较不相关的指针,请形成一个辅助函数less(const void *p1, const void *p2)
并在那里执行平台特定的代码。
也许:
// return -1,0,1 for <,==,>
int ptrcmp(const void *c1, const void *c1) {
// Equivalence test works on all platforms
if (c1 == c2) {
return 0;
}
// At this point, we know pointers are not equivalent.
#ifdef UINTPTR_MAX
uintptr_t u1 = (uintptr_t)c1;
uintptr_t u2 = (uintptr_t)c2;
// Below code "works" in that the computation is legal,
// but does it function as desired?
// Likely, but strange systems lurk out in the wild.
// Check implementation before using
#if tbd
return (u1 > u2) - (u1 < u2);
#else
#error TBD code
#endif
#else
#error TBD code
#endif
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
1662 次 |
最近记录: |