【问题标题】:Why does the restrict qualifier still allow memcpy to access overlapping memory?为什么限制限定符仍然允许 memcpy 访问重叠内存?
【发布时间】:2017-05-23 06:35:51
【问题描述】:

我想看看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,因为它保证不会重叠?

【问题讨论】:

  • memmove 不缓冲数据,它就像数据被缓冲一样。例如,这可以通过检查数据在内存中是向前还是向后移动,然后相应地从后向前或从前向后复制来完成。在实践中,它有点复杂,因为将数据复制回前面会出现性能问题,但这就是想法。
  • "restrict 不会阻止访问与memcpy 重叠的内存" - 你为什么期望它这样做?当memcpy 无论如何都试图访问重叠的内存(因为你告诉它访问重叠的内存)时,你期望会发生什么?
  • printf("%d\n", &amp;itself); 导致未定义的行为,%d 仅打印 int 但给出的参数是 char *
  • 这个问题从头到尾都没有意义。对象不能更改它们的地址,那你为什么要一遍又一遍地打印出itself 的地址呢?

标签: c memcpy restrict-qualifier


【解决方案1】:

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,但您需要为检查付费。

【讨论】:

    【解决方案2】:

    memcpy函数将n个字节从内存区src复制到内存区d​​est直接

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

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

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

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

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

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

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

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

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

    【讨论】:

      【解决方案3】:

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


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

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

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

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

      【讨论】:

      • "...它在某些情况下引入了未定义的行为" - 这让我笑了。
      猜你喜欢
      • 1970-01-01
      • 2015-03-12
      • 1970-01-01
      • 2016-08-13
      • 2023-04-07
      • 2013-06-07
      • 2019-06-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多