简体   繁体   English

uintptr_t 上的指针运算

[英]Pointer arithmetic on uintptr_t

I have to implement a "bandpass" filter.我必须实施“带通”滤波器。 Let a and b denote two integers that induce a half-open interval [a, b) .ab表示产生半开区间[a, b)的两个整数。 If some argument x lies within this interval (ie, a <= x < b ), I return a pointer to a C string const char* high , otherwise I return a pointer const char* low .如果某个参数x位于此区间内(即a <= x < b ),我返回一个指向 C 字符串const char* high的指针,否则我返回一个指针const char* low The vanilla implementation of this function looks like这个 function 的香草实现看起来像

const char* vanilla_bandpass(int a, int b, int x, const char* low,
    const char* high)
{
    const bool withinInterval { (a <= x) && (x < b) };
    return (withinInterval ? high : low);
}

which when compiled with -O3 -march=znver2 on Godbolt gives the following assembly code当在 Godbolt 上使用-O3 -march=znver2编译时,会给出以下汇编代码

vanilla_bandpass(int, int, int, char const*, char const*):
        mov     rax, r8
        cmp     edi, edx
        jg      .L4
        cmp     edx, esi
        jge     .L4
        ret
.L4:
        mov     rax, rcx
        ret

Now, I've looked into creating a version without a jump/branch, which looks like this现在,我研究了创建一个没有跳转/分支的版本,看起来像这样

#include <cstdint>

const char* funky_bandpass(int a, int b, int x, const char* low,
    const char* high)
{
    const bool withinInterval { (a <= x) && (x < b) };
    const auto low_ptr = reinterpret_cast<uintptr_t>(low) * (!withinInterval);
    const auto high_ptr = reinterpret_cast<uintptr_t>(high) * withinInterval;

    const auto ptr_sum = low_ptr + high_ptr;
    const auto* result = reinterpret_cast<const char*>(ptr_sum);
    return result;
}

which just is ultimately just a "chord" between two pointers.这最终只是两个指针之间的“和弦”。 Using the same options as before, this code compiles to使用与之前相同的选项,此代码编译为

funky_bandpass(int, int, int, char const*, char const*):
        mov     r9d, esi
        cmp     edi, edx
        mov     esi, edx
        setle   dl
        cmp     esi, r9d
        setl    al
        and     edx, eax
        mov     eax, edx
        and     edx, 1
        xor     eax, 1
        imul    rdx, r8
        movzx   eax, al
        imul    rcx, rax
        lea     rax, [rcx+rdx]
        ret

While at first glance, this function has more instructions, careful benchmarking shows that it's 1.8x to 1.9x faster than the vanilla_bandpass implementation.虽然乍一看,这个 function 有更多的指令,但仔细的基准测试表明它比vanilla_bandpass实现快 1.8 到 1.9 倍。

Is this use of uintptr_t valid and free of undefined behavior? uintptr_t的这种使用是否有效且没有未定义的行为? I'm well aware that the language around uintptr_t is vague and ambiguous to say the least, and that anything that isn't explicitly specified in the standard (like arithmetic on uintptr_t ) is generally considered undefined behavior.我很清楚围绕uintptr_t的语言至少可以说是模糊和模棱两可的,并且标准中未明确指定的任何内容(如uintptr_t上的算术)通常被认为是未定义的行为。 On the other hand, in many cases, the standard explicitly calls out when something has undefined behavior, which it also doesn't do in this case.另一方面,在许多情况下,标准会在某些事物具有未定义的行为时明确调用,而在这种情况下它也不会这样做。 I'm aware that the "blending" that happens when adding together low_ptr and high_ptr touches on topics just as pointer provenance, which is a murky topic in and of itself.我知道将low_ptrhigh_ptr加在一起时发生的“混合”触及主题,就像指针出处一样,这本身就是一个模糊的主题。

Is this use of uintptr_t valid and free of undefined behavior? uintptr_t的这种使用是否有效且没有未定义的行为?

Yes.是的。 Conversion from pointer to integer (of sufficient size such as uintptr_t ) is well defined, and integer arithmetic is well defined.从指针到 integer(具有足够大小,例如uintptr_t )的转换定义明确,integer 算术定义明确。

Another thing to be wary about is whether converting a modified uintptr_t back to a pointer gives back what you want.另一件需要注意的事情是将修改后的uintptr_t回指针是否会返回您想要的内容。 Only guarantee given by the standard is that pointer converted to integer converted back gives the same address.标准给出的唯一保证是转换为 integer 的指针转换回给出相同的地址。 Luckily, this guarantee is sufficient for you, because you always use the exact value from a converted pointer.幸运的是,这种保证对你来说已经足够了,因为你总是使用来自转换指针的精确值。

If you were using something other than pointer to narrow character, I think you would need to use std::launder on the result of the conversion.如果您使用的不是指向窄字符的指针,我认为您需要对转换结果使用std::launder

The Standard doesn't require that implementations process uintptr_t -to-pointer conversions in useful fashion even in cases where the uintptr_t values are produced from pointer-to-integer conversions.该标准不要求实现以有用的方式处理uintptr_t到指针的转换,即使在uintptr_t值是从指针到整数转换产生的情况下也是如此。 Given eg给例如

extern int x[5],y[5];
int *px5 = x+5, *py0 = y;

the pointers px5 and py0 might compare equal, and regardless of whether they do or not, code may use px5[-1] to access x[4] , or py0[0] to access y[0] , but may not access px5[0] nor py0[-1] .指针px5py0可能比较相等,无论它们是否相等,代码都可以使用px5[-1]访问x[4] ,或py0[0]访问y[0] ,但不能访问px5[0]也不py0[-1] If the pointers happen to be equal, and code attempts to access ((int*)(uintptr_t)px5)[-1] , a compiler could replace (int*)(uintptr_t)px5) with py0 , since that pointer would compare equal to px5 , but then jump the rails when attempting to access py0[-1] .如果指针恰好相等,并且代码尝试访问((int*)(uintptr_t)px5)[-1] ,编译器可以将(int*)(uintptr_t)px5)替换为py0 ,因为该指针比较相等到px5 ,然后在尝试访问py0[-1]时跳过轨道。 Likewise if code tries to access ((int*)(uintptr_t)py0)[0] , a compiler could replace (int*)(uintptr_t)py0 with px5 , and then jump the rails when attempting to access px5[0] .同样,如果代码尝试访问((int*)(uintptr_t)py0)[0] ,编译器可以将(int*)(uintptr_t)py0px5 ,然后在尝试访问px5[0]时跳过轨道。

While it may seem obtuse for a compiler to do such a thing, clang gets even crazier.虽然编译器做这样的事情看起来很迟钝,但 clang 变得更加疯狂。 Consider:考虑:

#include <stdint.h>
extern int x[],y[];
int test(int i)
{
    y[0] = 1;
    uintptr_t px = (uintptr_t)(x+5);
    uintptr_t py = (uintptr_t)(y+i);
    int flag = (px==py);
    if (flag)
        y[i] = 2;
    return y[0];
}

If px and py are coincidentally equal and i is zero, that will cause clang to set y[0] to 2 but return 1. See https://godbolt.org/z/7Sa_KZ for generated code ("mov eax,1 / ret" means "return 1").如果pxpy恰好相等且i为零,这将导致 clang 将y[0]设置为 2 但返回 1。有关生成的代码,请参阅https://godbolt.org/z/7Sa_KZ ("mov eax,1 / ret”的意思是“返回 1”)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM