【问题标题】:Can the following code be true for pointers to different things以下代码对于指向不同事物的指针是否正确
【发布时间】:2017-01-02 18:58:54
【问题描述】:

我有一段我正在“守护”的记忆,由

定义
typedef unsigned char byte;

byte * guardArea;
size_t guardSize;

byte * guardArea = getGuardArea();
size_t guardSize = getGuardSize();

为此,可接受的实现是:

size_t glGuardSize = 1024; /* protect an area of 1kb */
byte * getGuardArea()
{
     return malloc( glGuardSize );
}
size_t getGuardSize()
{
     return glGuardSize;
}

下面的 sn-p 可以为任何指针(来自不同的 malloc、来自堆栈等)返回 true 吗?

if ( ptr >= guardArea && ptr < (guardArea + guardSize)) {
     return true;
}

标准规定:

  • 区域内的值将返回 true。 (当 ptr 是成员时,所有行为都正确。)

  • 指针将是不同的(a == b 仅当它们相同时)。

  • 字节数组中的所有地址都可以通过递增基数来访问。
  • 任何指针都可以与 char * 相互转换,而不会造成损坏。

所以我无法理解来自不同对象的任何指针的结果如何为真(因为它会破坏该区域内指针之一的不同规则)。

编辑:

用例是什么?

检测指针是否在区域内的能力非常重要,有时会编写代码

if (  isInMyAreaOfInterest( unknownPointer ) ) {
    doMySpecialThing( unknownPointer );
} else {
    doSomethingElse( unknownPointer );
}

我认为语言需要通过使此类结构简单明了来支持开发人员,而我们对标准的解释是开发人员需要强制转换为 int。由于不同对象的指针比较的“未定义行为”。

我希望能弄清楚为什么我不能做我想做的事(我的 sn-p),因为我发现的所有关于 SO 的帖子都说该标准声称未定义的行为,没有任何解释或示例为什么这个标准比我想要的更好。

目前,我们有一条规则,我们既不理解为什么存在这条规则,也不怀疑这条规则是否对我们有帮助

示例帖子:

SO: checking if a pointer is in a malloced area

SO: C compare pointers

【问题讨论】:

  • 其实依赖于getGuardArea()getGuardSize();的实现。只要你不详细说明,这个问题是无法回答的。
  • 只是为了好奇,用例是什么?
  • 我仍然不明白你的问题。如果您使用malloc 获得一些内存,则可以确定不会再次分配该内存块(除非您系统上的malloc 严重损坏)。从概念上讲,您的 sn-p 有效。有什么问题?指针的铸造?你能链接到这些讨论 UB 的帖子吗?
  • 所以我无法理解来自不同对象的任何指针的结果如何为真。问题的前提是错误的。如果使用关系运算符比较指向不同对象的两个指针,则会导致未定义的行为。结果不是假或真,而是允许发生任何事情,因为它是未定义的行为,在这里解释:stackoverflow.com/a/4105123/4082723
  • @Lundin:由非迟钝的程序员为现代线性地址机器编写的编译器不应该将关系运算符解释为在所有数据指针之间定义一致的非重叠排序,这是没有现实原因的。不幸的是,几十年来一直被正确识别为迟钝的编译器行为今天被认为是时尚的。

标签: c pointers language-lawyer


【解决方案1】:

尽管指针未指向区域,但分配仍然可能生成满足条件的指针。例如,这将发生在处于保护模式的 80286 上,Windows 3.x 在标准模式和 OS/2 1.x 中使用该模式。

在这个系统中,指针是 32 位值,分为两个 16 位部分,传统上写为XXXX:YYYY。第一个 16 位部分 (XXXX) 是“选择器”,它选择 64KB 的存储区。第二个 16 位部分 (YYYY) 是“偏移量”,它选择该 64KB 库中的一个字节。 (它比这更复杂,但为了讨论的目的,让我们把它留在那里。)

大于 64KB 的内存块被分成 64KB 的块。要从一个块移动到下一个块,请向选择器添加 8。比如0101:FFFF后面的字节是0109:0000

但是为什么要加 8 来移动到下一个选择器呢?为什么不增加选择器?因为选择器的后三位是用来做其他事情的。

特别是选择器的底部位用于选择选择器表。 (让我们忽略第 1 位和第 2 位,因为它们与讨论无关。为方便起见,假设它们始终为零。)

有两个选择器表,全局选择器表(用于所有进程共享的内存)和本地选择器表(用于单个进程专用的内存)。因此,进程私有内存可用的选择器有0001000900110019等。同时,全局内存可用的选择器有000800100018、@ 987654333@等(选择器0000保留。)

好的,现在我们可以设置反例了。假设guardArea = 0101:0000guardSize = 0x00020000。这意味着受保护的地址是0101:00000101:FFFF0109:00000109:FFFF。此外,guardArea + guardSize = 0111:0000.

同时,假设有一些全局内存恰好分配在0108:0000。这是全局内存分配,因为选择器是偶数。

观察到全局内存分配不是保护区域的一部分,但它的指针值确实满足数字不等式0101:0000 &lt;= 0108:0000 &lt; 0111:0000

额外奖励:即使在具有平坦内存模型的 CPU 架构上,测试也可能失败。现代编译器利用未定义的行为并相应地进行优化。如果他们看到指针之间的关系比较,则允许他们假设指针指向同一个数组(或超过该数组的最后一个元素)。具体来说,唯一可以合法地与guardArea 比较的指针是guardAreaguardArea+1guardArea+2、...、guardArea + guardSize 形式的指针。对于所有这些指针,条件ptr &gt;= guardArea 为真,因此可以优化,将您的测试减少到

if (ptr < (guardArea + guardSize))

现在将满足数值小于guardArea 的指针。

故事寓意:这段代码不安全,即使在平面架构上也不安全。

但一切都没有丢失:指针到整数的转换是实现定义的,这意味着您的实现必须记录它是如何工作的。如果您的实现将指针到整数的转换定义为生成指针的数值,并且您知道自己处于平面架构上,那么您可以做的是比较 integers 而不是 指针。整数比较不像指针比较那样受到约束。

if ((uintptr_t)ptr >= (uintptr_t)guardArea &&
    (uintptr_t)ptr < (uintptr_t)guardArea + (uintptr_t)guardSize)

【讨论】:

  • @Lundin 问题并没有说“假设我不在一个 icky 处理器上。”另请参阅额外的喋喋不休,这表明即使在现代平面架构上,这段代码也是不安全的。
  • 这个答案的 "Bonus chatter" 部分完美地说明了当代(2016 年)优化编译器的一个主要缺陷。如果程序员写了if (ptr&gt;=guardArea &amp;&amp; ptr&lt;(guardArea + guardSize)) 并且编译器默默地放弃了第一个条件,那么那个编译器就严重损坏了。检测未定义的行为不是优化的机会,它发出诊断的机会。
  • @user3386109 另一方面,有时您想要优化。例如。 bool inRange(char*p,char*start, int length){return p&gt;=start &amp;&amp; p&lt;start+length); int array[80]; void stuff(){char*p = findSomething(array, 80); /* stop if not found */ if (!p) return; /* if in first half */ if (inRange(p, array, 40)) { ... } /* if in second half */ if (inRange(p, array+40, 40)) { ... } }。在这种情况下,您想要在检查数组的前半部分时优化检查的前半部分,如果编译器吐出诊断信息,您会很恼火。
  • @user3386109 我应该放弃尝试提出具体的例子。关键是内联、循环展开、常量传播、模板扩展和许多其他编译器转换可以暴露您希望编译器利用的有效优化机会。
  • @YuhongBao 我知道,但为了讨论的目的我简化了架构。 LDT 位是 0、1 还是 2 无关紧要。
【解决方案2】:

是的。

void foo(void) {}
void(*a) = foo;
void *b = malloc(69);
uintptr_t ua = a, ub = b;

uaub 实际上被允许具有相同的值。这在分段系统(如 MS-DOS)上经常发生,可能会将代码和数据放在单独的段中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-30
    • 1970-01-01
    • 2020-01-18
    • 1970-01-01
    相关资源
    最近更新 更多