为什么restrict限定符仍允许memcpy访问重叠内存?

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

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

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

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

 #include  #include  #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
停止undefined bop undefined行为?
地址:12345

memcpy是否使用独立指针? 因为输出肯定显示未定义的行为,而restrict不会阻止使用memcpy访问重叠内存。

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

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 。 如果不这样做,请使用memmovememmove通常会检查是否有重叠,如果没有,最终会使用memcpy ,但是您支付支票。

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

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

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

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

memcpy是否使用独立指针? 因为输出肯定显示未定义的行为,而restrict不会阻止使用memcpy访问重叠内存。

restrict不会阻止任何事情。 编译器不需要诊断甚至注意到您将别名指针传递给restrict -qualified参数。 实际上,这种决定通常不能在编译时完成。

实际上,当您使用在第一次调用中执行的参数调用memcpy()时,您确实提供了两个与源和目标参数相同的对象的独立指针。 结果,程序的行为是不确定的。

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

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

这是一个意见问题,因此在这里偏离主题。 我只会说,通过首先测量 ,然后根据这些测量的结果做出决策,最好地解决性能问题。 而且,不同实现的性能特征可以变化。

restrict永远不会停止未定义的行为。 事实上,它在某些情况下引入了未定义的行为。 如果从没有UB的代码中删除restrict ,那么代码仍然没有UB; 但反过来却不是这样。


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

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

由于溢出了分配的缓冲区的大小。 之后,所有赌注都已关闭。

restrict限定符不会阻止缓冲区溢出。