1

我想看看是否restrict会阻止memcpy访问重叠的内存。

memcpy函数将n个字节从内存区 src直接复制到内存区 dest 。内存区域不应重叠。

memmove使用缓冲区,因此没有重叠内存的风险。

限定符表示在指针的restrict生命周期内,只有指针本身或直接来自它的值(例如pointer + n)才能访问该对象的数据。如果不遵循意图声明并且通过独立指针访问对象,这将导致未定义的行为。

#include <stdio.h>
#include <string.h>

#define SIZE 30

int main ()
{
    char *restrict itself;
    itself = malloc(SIZE);
    strcpy(itself, "Does restrict stop undefined behavior?");
    printf("%d\n", &itself);
    memcpy(itself, itself, SIZE);
    puts(itself);
    printf("%d\n", &itself);

    memcpy(itself-14, itself, SIZE); //intentionally trying to access restricted memory
    puts(itself);
    printf("%d\n", &itself);

    return (0);
}

输出 ()

自身地址:12345
限制是否停止未定义的行为?
自身地址:12345
停止未定义的 bop 未定义行为?
自身地址:12345

是否memcpy使用独立指针?因为输出肯定会显示未定义的行为,restrict并且不会阻止使用memcpy.

我假设memcpy它具有性能优势,因为它在memmove使用缓冲区时直接复制数据。但是对于现代计算机,我是否应该忽略这种潜在的更好性能并始终使用它,memmove因为它保证没有重叠?

4

3 回答 3

5

关键字是提供给编译器以允许生成代码的restrict提示,告诉编译器它不应该被指针别名的可能性所困扰(两个不同的命名指针访问相同的地址)。

在一个接受指针的函数中restrict,编译器知道写入一个指针不会影响另一个。从位置 A 复制到位置 B 时,这意味着它可以安全地更改此代码:

  • 从 A[0] 读入寄存器 1
  • 从寄存器 1 写入 B[0]
  • 从 A[1] 读入寄存器 1
  • 从寄存器 1 写入 B[1]
  • 从 A[2] 读入寄存器 1
  • 从寄存器 1 写入 B[2]
  • 从 A[3] 读入寄存器 1
  • 从寄存器 1 写入 B[3]
  • ...

进入这个序列:

  • 从 A[0] 读入寄存器 1
  • 从 A[1] 读入寄存器 2
  • 从 A[2] 读入寄存器 3
  • 从 A[3] 读入寄存器 4
  • 从寄存器 1 写入 B[0]
  • 从寄存器 2 写入 B[1]
  • 从寄存器 3 写入 B[2]
  • 从寄存器 4 写入 B[3]
  • ...

仅当 A 和 B 不重叠并且编译器不会优化到第二个序列时,这两个序列才是相同的,除非您使用restrict(或者除非它可以从上下文中猜测这样做是安全的)。

如果您最终提供了一个不期望它们的函数的别名指针,编译器可能会在编译时警告您,但不能保证它会。但是您真的不应该期望在编译时出现错误 - 相反,您应该将其视为当编译器从您的代码生成程序集时授予编译器的权限。


回答您关于性能的问题 - 如果您确定指针中没有重叠,请使用memcpy. 如果不这样做,请使用memmove. memmove通常会检查是否有重叠,memcpy如果没有,则最终使用,但您支付检查费用。

于 2017-01-09T04:14:23.223 回答
3

memcpy函数将n个字节从内存区 src直接复制到内存区 dest 。

“直接”我想您的意思是该函数避免首先从源复制到缓冲区,然后从缓冲区复制到目标。虽然这很可能是真的,但标准并不要求它是真的。

memmove使用缓冲区,因此没有重叠内存的风险。

不,memmove会产生一个结果,就好像它首先复制到缓冲区并从那里复制到目标一样。不需要以这种方式实际使用缓冲区,只要它产生所需的结果即可。任何给定的实现可能会或可能不会这样做。

是否memcpy使用独立指针?因为输出肯定会显示未定义的行为,restrict并且不会阻止使用memcpy.

restrict永远不会阻止任何事情。编译器不需要诊断甚至注意到您将别名指针传递给restrict-qualified 参数。实际上,通常根本无法在编译时做出该决定。

事实上,当您memcpy()使用您在第一次调用中所做的参数调用时,您确实提供了两个独立的指针,指向同一个对象作为源参数和目标参数。结果,程序的行为是未定义的。

由于计算,您的程序还表现出未定义的行为itself - 14(无论结果指针是否被取消引用)。如果itself改为指向分配对象内部的至少 14 个字节,以使指针算法有效,那么第二次memcpy()调用的参数将再次与参数限定的要求不一致restrict,因此程序将显示 UB出于这个原因,也是。

我假设memcpy它具有性能优势,因为它在memmove使用缓冲区时直接复制数据。但是对于现代计算机,我是否应该忽略这种潜在的更好性能并始终使用它,memmove因为它保证没有重叠?

这是一个见仁见智的问题,因此这里是题外话。我只想说,最好通过首先测量来解决性能问题,然后根据这些测量的结果做出决定。此外,不同实现的性能特征可能会有所不同。

于 2017-01-09T04:20:43.093 回答
2

restrict永远不会停止未定义的行为。事实上,它在某些情况下会引入未定义的行为。如果restrict从一段没有UB的代码中去掉,那么这段代码仍然没有UB;但反之则不然。


您的代码在此行导致未定义的行为:

strcpy(itself, "Does restrict stop undefined behavior?");

由于溢出分配的缓冲区的大小。在那之后,所有的赌注都被取消了。

restrict限定符不能防止缓冲区溢出。

于 2017-01-09T04:40:32.227 回答