以下代码可以用于指向不同事物的指针

mks*_*eve 13 c pointers language-lawyer

我有一段记忆,我正在"守卫",定义为

typedef unsigned char byte;

byte * guardArea;
size_t guardSize;

byte * guardArea = getGuardArea();
size_t guardSize = getGuardSize();
Run Code Online (Sandbox Code Playgroud)

为此目的可接受的实现是:

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

以下代码段可以为任何指针(来自不同的malloc,来自堆栈等)返回true吗?

if ( ptr >= guardArea && ptr < (guardArea + guardSize)) {
     return true;
}
Run Code Online (Sandbox Code Playgroud)

该标准规定:

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

  • 指针将是不同的(a == b只有它们是相同的).

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

所以我无法理解结果对于来自不同对象的任何指针的结果是否正确(因为它会破坏区域内其中一个指针的不同规则).

编辑:

用例是什么?

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

if (  isInMyAreaOfInterest( unknownPointer ) ) {
    doMySpecialThing( unknownPointer );
} else {
    doSomethingElse( unknownPointer );
}
Run Code Online (Sandbox Code Playgroud)

我认为语言需要通过使这样的结构简单明了来支持开发人员,并且我们对标准的解释是开发人员需要转换为int.由于不同对象的指针比较的"未定义行为".

我希望能清楚地知道为什么我不能做我想做的事情(我的片段),因为我发现的所有帖子都说标准声称未定义的行为,没有任何解释,或者为什么标准更好的例子比我希望它如何工作.

目前,我们有一条规则,我们既不理解规则存在的原因,也不了解规则是否有助于我们

示例帖子:

SO:检查指针是否在malloced区域

SO:C比较指针

Ray*_*hen 35

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

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

大于64KB的内存块被分解为64KB块.要从一个块移动到下一个块,请将8添加到选择器.例如,之后的字节0101:FFFF0109:0000.

但是为什么要添加8来移动到下一个选择器呢?为什么不只是增加选择器?因为选择器的底部三位用于其他事情.

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

有两个选择器表,全局选择器表(用于跨所有进程共享的内存)和本地选择器表(用于单个进程的私有内存).因此,可用于进程的私有内存的选择是0001,0009,0011,0019,等.同时,可为全球内存的选择是0008,0010,0018,0020,等(选择0000保留.)

好的,现在我们可以设置我们的反例.假设guardArea = 0101:0000guardSize = 0x00020000.这意味着守卫地址是0101:0000通过0101:FFFF0109:0000通过0109:FFFF.而且,guardArea + guardSize = 0111:0000.

同时,假设有一些碰巧分配的全局内存0108:0000.这是全局内存分配,因为选择器是偶数.

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

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

if (ptr < (guardArea + guardSize))
Run Code Online (Sandbox Code Playgroud)

现在将满足数字小于的指针guardArea.

故事的道德:这段代码不安全,甚至在平面架构上也是如此.

但并没有丢失:指向整数的转换是实现定义的,这意味着您的实现必须记录它的工作原理.如果您的实现将指针到整数的转换定义为生成指针的数值,并且您知道自己处于扁平体系结构中,那么您可以做的是比较整数而不是指针.整数比较的约束方式与指针比较的方式不同.

if ((uintptr_t)ptr >= (uintptr_t)guardArea &&
    (uintptr_t)ptr < (uintptr_t)guardArea + (uintptr_t)guardSize)
Run Code Online (Sandbox Code Playgroud)

  • 这个答案的*"Bonus chatter"*部分完美地说明了当代(2016)优化编译器的一个主要缺陷.如果程序员写了`if(ptr> = guardArea && ptr <(guardArea + guardSize))`并且编译器默默地删除了第一个条件,那么该编译器就会严重破坏.检测未定义的行为不是优化的机会,它是***发布诊断的机会. (7认同)
  • @ user3386109我应该放弃尝试提出具体的例子.关键是内联,循环展开,常量传播,模板扩展和许多其他编译器转换可以暴露您希望编译器利用的有效优化机会. (7认同)
  • @Lundin问题没有说"假设我不是一个icky处理器." 另请参阅奖金聊天,它表明即使在现代平面架构上,此代码也不安全. (5认同)
  • @ user3386109 另一方面,有时您*想要*优化。例如`bool inRange(char*p,char*start, int length){return p&gt;=start &amp;&amp; p&lt;start+length); 整数数组[80]; void stuff(){char*p = findSomething(array, 80); /* 如果没有找到就停止 */ if (!p) return; /* 如果在前半部分 */ if (inRange(p, array, 40)) { ... } /* 如果在后半部分 */ if (inRange(p, array+40, 40)) { ... } }`。在这种情况下,您*希望*在检查数组的前半部分时优化检查的前半部分,如果编译器输出诊断信息,您会很生气。 (3认同)