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

时间:2017-01-09 03:24:39

标签: c memcpy restrict-qualifier

我想知道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
  停止undefined bop undefined行为?
  地址:12345

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

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

3 个答案:

答案 0 :(得分:3)

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不重叠且编译器不会优化到第二个序列时才相同,除非您使用memcpy(或者除非可以从上下文中猜测它是安全的) )。

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

回答关于效果的问题 - 如果您确定指针中没有重叠,请使用memmove。如果您不这样做,请使用memmovememcpy通常会检查是否存在重叠,如果没有switch(growth){ case 0: document.getElementById("evolutionButton").style.backgroundColor = "green"; break; case 1: document.getElementById("evolutionButton").style.backgroundColor = "Yellow"; break; case 2: document.getElementById("evolutionButton").style.backgroundColor = "orange"; break; case 3: document.getElementById("evolutionButton").style.backgroundColor = "red"; break; case 4: document.getElementById("evolutionButton").style.backgroundColor = "purple"; } ,最终会使用{document.getElementById("evolutionButton").className = "green";},但您需要支付支票。

答案 1 :(得分:2)

  

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

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

  

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

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

  

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

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

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

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

  

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

这是一个意见问题,因此在这里偏离主题。我只会说,首先测量最好接近性能问题,然后根据这些测量结果做出决策。此外,不同实现的性能特征可能会有所不同。

答案 2 :(得分:2)

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

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

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

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

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