C 在x86和x64上读取同一页中的缓冲区末尾是否安全?
如果允许在输入缓冲区末尾读取少量数据,那么高性能算法中的许多方法都可以简化。这里,“小数量”通常指超过末尾的最多C 在x86和x64上读取同一页中的缓冲区末尾是否安全?,c,performance,assembly,optimization,x86,C,Performance,Assembly,Optimization,X86,如果允许在输入缓冲区末尾读取少量数据,那么高性能算法中的许多方法都可以简化。这里,“小数量”通常指超过末尾的最多W-1字节,其中W是算法的字大小(以字节为单位)(例如,对于以64位块处理输入的算法,最多7个字节) 很明显,一般来说,在输入缓冲区结束后写入数据是不安全的,因为您可能会将数据删除到缓冲区1之外。同样清楚的是,从缓冲区的末尾读入另一页可能会触发分段错误/访问冲突,因为下一页可能不可读 然而,在读取对齐值的特殊情况下,页面错误似乎是不可能的,至少在x86上是这样。在该平台上,页面(以及内
W-1
字节,其中W
是算法的字大小(以字节为单位)(例如,对于以64位块处理输入的算法,最多7个字节)
很明显,一般来说,在输入缓冲区结束后写入数据是不安全的,因为您可能会将数据删除到缓冲区1之外。同样清楚的是,从缓冲区的末尾读入另一页可能会触发分段错误/访问冲突,因为下一页可能不可读
然而,在读取对齐值的特殊情况下,页面错误似乎是不可能的,至少在x86上是这样。在该平台上,页面(以及内存保护标志)具有4K粒度(可以使用更大的页面,例如2MiB或1GiB,但这些页面是4K的倍数),因此对齐读取将仅访问与缓冲区有效部分相同页面中的字节
下面是一个典型的循环示例,该循环将其输入对齐,并读取超过缓冲区末尾7个字节的数据:
int processBytes(uint8_t *input, size_t size) {
uint64_t *input64 = (uint64_t *)input, end64 = (uint64_t *)(input + size);
int res;
if (size < 8) {
// special case for short inputs that we aren't concerned with here
return shortMethod();
}
// check the first 8 bytes
if ((res = match(*input)) >= 0) {
return input + res;
}
// align pointer to the next 8-byte boundary
input64 = (ptrdiff_t)(input64 + 1) & ~0x7;
for (; input64 < end64; input64++) {
if ((res = match(*input64)) > 0) {
return input + res < input + size ? input + res : -1;
}
}
return -1;
}
int processBytes(uint8\u t*input,size\u t size){
uint64_t*输入64=(uint64_t*)输入,end64=(uint64_t*)(输入+大小);
国际关系;
如果(尺寸<8){
//我们这里不关心的短输入的特殊情况
返回shortMethod();
}
//检查前8个字节
如果((res=match(*input))>=0){
返回输入+res;
}
//将指针与下一个8字节边界对齐
input64=(ptrdiff_t)(input64+1)和~0x7;
对于(;input640){
返回input+res
内部函数int match(uint64\u t bytes)
未显示,但它查找与特定模式匹配的字节,如果找到,则返回该位置的最低值(0-7),否则返回-1
首先,大小小于8的案例被典当到另一个函数,以简化说明。然后对前8个字节(未对齐字节)执行一次检查。然后对剩余的楼层((大小-7)/8)
块8字节2执行循环。此循环最多可以读取超过缓冲区末尾的7个字节(当输入&0xF==1
时会出现7个字节的情况)。但是,return调用有一个检查,它排除了缓冲区末尾以外发生的任何虚假匹配
实际上,这样的函数在x86和x86-64上安全吗?
这些类型的超读在高性能代码中很常见。避免这种过度阅读的特殊尾部代码也很常见。有时,您会看到后一种类型取代了前一种类型,使valgrind等工具静音。有时,您会看到这样一个替代方案,但由于习惯用法是安全的,并且工具出错(或者过于保守)而被拒绝
语言律师须知:
绝对不允许读取超出其分配大小的指针
在标准中。我欣赏律师回答的语言,甚至偶尔写信
我自己写的,当有人翻阅这一章时,我甚至会很高兴
显示上述代码的诗句是未定义的行为,因此
严格意义上讲不安全(我将在这里复制细节)。但归根结底,这不是问题所在
我在找你。实际上,许多涉及指针的常用习惯用法
转换、通过此类指针进行结构访问等等
技术上未定义,但在高质量和高性能方面广泛存在
性能代码。通常没有替代品,或者替代品
以半速或更低的速度运行
如果你愿意,考虑这个问题的修改版本,即:
在上述代码已编译为x86/x86-64程序集,且用户已验证其以预期方式编译后(即。, 编译器没有使用可证明的部分越界访问 做点什么, 执行编译后的程序安全吗 在这方面,这个问题既是一个C问题,也是一个x86汇编问题。我所看到的大多数使用这种技巧的代码都是用C编写的,而C仍然是高性能库的主导语言,很容易超越像asm这样的低级语言和像asm这样的高级语言。至少在核心数字领域之外FORTRAN仍在发挥作用,所以我对这个问题的C-compiler-and-down视图感兴趣,这就是为什么我没有将它表述为一个纯粹的x86汇编问题 尽管如此,我对链接到 标准显示这是环球开发商,我非常感兴趣的任何细节 可以使用此特定UD生成 意外的代码。现在我认为如果没有一些深层次的 非常深入的跨过程分析,但是gcc溢出的东西 也让很多人感到惊讶1即使在明显无害的情况下,例如,在回写相同值的情况下,它也可以 2注意:要使此重叠工作,需要此函数和
match()
函数以特定的幂等方式工作,特别是返回值支持重叠检查。因此,“查找第一个字节匹配模式”工作,因为所有match()
调用仍然有序。“计数字节匹配模式”但是,方法不起作用,因为有些字节可能会被重复计数。顺便说一下:有些函数,例如“returntheminimumbyte”调用,即使没有顺序限制也可以工作,但需要检查所有字节
3这里值得注意的是,对于valgrind的Memcheck,--部分加载ok
,它控制这样的读取是否实际上被报告为错误。默认值为yes,这意味着一般情况下,这样的加载不会被视为立即错误,而是