kqr*_*kqr 10 c malloc glibc language-lawyer
指针与关系运算符相比较(例如<,<=,>=或>)仅由C标准定义当相同的聚合对象(结构,阵列或联合)内的指针都指向.这在实践中意味着形状的比较
if (start_object <= my_pointer && my_pointer < end_object+1) {
Run Code Online (Sandbox Code Playgroud)
可以变成
if (1) {
Run Code Online (Sandbox Code Playgroud)
通过优化编译器.尽管如此,在K&R的第8.7节"示例 - 存储分配器"中,作者进行了与上述类似的比较.他们原谅这个说法
然而,仍然有一个假设是,
sbrk可以有意义地比较返回的不同块的指针.标准不保证这一点,它只允许在数组中进行指针比较.因此,这个版本malloc只能在一般指针比较有意义的机器之间移植.
此外,它出现的执行malloc用在glibc做同样的事情!
更糟糕的是 - 我偶然发现这一点的原因是 - 对于学校作业我应该实现一个基本的malloc功能,并且作业的指示要求我们使用K&R代码,但我们必须更换sbrk拨打电话mmap!
虽然比较来自不同sbrk调用的指针可能是未定义的,但它也只是略微可疑,因为你有某种心理直觉,返回的指针应该来自同一个内存区域.mmap根据我的理解,不同调用返回的指针甚至不能保证彼此远程相似,并且在mmap调用之间合并/合并内存块应该是非常非法的(并且它似乎glibc避免了这种情况,只需合并返回的内存)通过sbrk或内部内mmap,而不是在它们之间)的页面,但分配需要这个.
问题:有人可以发光
sbrk可以优化比较来自不同呼叫的指针,以及glibc那会让他们逃脱它.语言律师答案是(我相信)可以在C99标准的§6.5.8.5中找到(或者更确切地说来自ISO/IEC 9899:TC3委员会草案 - 2007年7月7日WG14/N1256,它几乎相同,但我不是吨有原来的手),其具有关于关系运算符(下面即<,<=,>,>=):
比较两个指针时,结果取决于指向的对象的地址空间中的相对位置.如果指向对象或不完整类型的两个指针都指向同一个对象,或者两个指针都指向同一个数组对象的最后一个元素,则它们相等.如果指向的对象是同一聚合对象的成员,则指向稍后声明的结构成员的指针比指向结构中先前声明的成员的指针大,指向具有较大下标值的数组元素的指针比指向同一数组的元素的指针大.具有较低的下标值.指向同一个union对象的成员的所有指针都比较相等.如果表达式
P指向数组对象的元素,并且表达式Q指向同一数组对象的最后一个元素,则指针表达式Q+1将比较大于P.在所有其他情况下,行为未定义.
(C11文本相同或几乎相同)
这开始似乎没有用,或者至少表明每个实现都利用未定义的行为.但是,我认为,您可以合理化行为或使用解决方法.
指定的C指针要么是NULL,要么是通过获取对象的地址&,或通过指针算术,或通过某个函数的结果得到的.在相关的情况下,它们由结果获得sbrk或mmap系统调用.这些系统真正回归的是什么?在寄存器级别,它们返回一个大小uintptr_t(或intptr_t)的整数.它实际上是系统调用接口,它将它们转换为指针.因为我们知道指针之间的转换和uintptr_t(或intptr_t)是双射类型的定义,我们知道我们可以将指针转换为uintptr_t(例如)并比较它们,这将对指针强加一个良好的顺序关系.维基百科链接提供了更多信息,但这实质上将确保每个比较都得到很好的定义以及其他有用的属性,例如a<b和b<c暗示a<c.(我也不能选择一个完全随意的顺序,因为它需要满足C99§6.5.8.5的其他要求,这几乎让我intptr_t和uintptr_t候选人一样.)
我们可以利用它来编写(可以说更好):
if ((uintptr_t)start_object <= (uintptr_t)my_pointer && (uintptr_t)my_pointer < (uintptr_t)(end_object+1)) {
Run Code Online (Sandbox Code Playgroud)
这里有一个尼特.你会注意到我投了但uintptr_t不是intptr_t.为什么这是正确的选择?事实上,为什么我没有选择一个相当奇怪的顺序,如反转位和比较?这里的假设是我选择与内核相同的顺序,特别是我的定义<(由排序给出)使得任何分配的内存块的开始和结束总是如此start < end.在我所知道的所有现代平台上,没有"环绕"(例如内核不会分配32位内存,从开始到0xffff8000结束0x00007ffff) - 尽管注意到过去已经开发过类似的环绕.
C标准规定指针比较在许多情况下给出未定义的结果.但是,在这里,您要使用系统调用返回的整数构建自己的指针.因此,您可以比较整数,或者通过将它们转换回整数来比较指针(利用演员的双射性质).如果你只是比较指针,你依赖于C编译器的指针比较实现是理智的,几乎可以肯定,但不能保证.
我提到的可能性是如此模糊,以至于可以打折吗?不,让我们找一个它们可能很重要的平台示例:8086.可以想象一个8086编译模型,其中每个指针都是一个"远"指针(即包含一个段寄存器).指针比较可以做一个<或>上段寄存器且仅当它们相等做一个<或>到偏置.只要C99§6.5.8.5中的所有结构属于同一段,这就完全合法.但它不会像人们预期的那样在段之间工作1000:1234(因为它等于1010:1134内存地址)会显得小于1010:0123.mmap这里可能会在不同的细分中返回结果.类似地,人们可以想到另一个存储器模型,其中段寄存器实际上是一个选择器,并且指针比较使用处理器比较指令来比较存储器地址,如果使用无效选择器或段外的偏移则中止该存储器地址.
你问两个具体的问题:
是否
sbrk可以优化比较来自不同呼叫的指针,以及如果是这样,glibc会做什么让他们逃脱它.
在上面给出的公式start_object中,实际上是等等void *,那么计算可以被优化掉(即可能做你想要的),但不能保证这样做,因为行为是未定义的.如果内核使用与强制转换所暗示的相同的井顺序,则转换将保证它这样做.
在回答第二个问题时,glibc依赖于C编译器的行为,这在技术上并不是必需的,但非常可能(根据上述内容).
另请注意(至少在我前面的K&R中)您引用的行在代码中不存在.警告与header *指针的比较有关<(因为我可以看到void *指针的比较<总是UB),这可能来自单独的sbrk()调用.
| 归档时间: |
|
| 查看次数: |
356 次 |
| 最近记录: |