为什么 restrict 限定符仍然允许 memcpy 访问重叠内存?
Why does the restrict qualifier still allow memcpy to access overlapping memory?
我想看看 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);
}
输出()
Address of itself: 12345
Does restrict stop undefined behavior?
Address of itself: 12345
stop undefined bop undefined behavior?
Address of itself: 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
。如果不这样做,请使用 memmove
。 memmove
通常会检查是否存在重叠,如果没有重叠则最终使用 memcpy
,但您需要为检查付费。
The memcpy
function copies n bytes from memory area src to memory area dest directly.
"directly" 我想你的意思是该函数避免先从源复制到缓冲区,然后再从缓冲区复制到目标。虽然这很可能是真的,但标准并不要求它是真的。
memmove
uses a buffer so there is no risk of overlapping memory.
不,memmove
产生结果 就好像 它首先复制到缓冲区,然后从那里复制到目的地。不需要以这种方式实际使用缓冲区,只要它产生所需的结果即可。任何给定的实现可能会或可能不会这样做。
Does memcpy
use an independent pointer? Because the output definitely shows undefined behavior and restrict
doesn't prevent access to overlapping memory with memcpy
.
restrict
永远不会 阻止 任何事情。编译器不需要诊断,甚至不需要注意到您将别名指针传递给 restrict
限定的参数。事实上,这种决定通常根本无法在编译时做出。
事实上,当您使用第一次调用时使用的参数调用 memcpy()
时,您确实提供了两个独立的指针,指向与源参数和目标参数相同的对象。因此,程序的行为是未定义的。
由于计算 itself - 14
,您的程序还表现出未定义的行为(无论结果指针是否曾经被解除引用)。如果 itself
指向分配对象内部至少 14 个字节,这样指针算法是有效的,那么第二个 memcpy()
调用的参数将再次与参数的 restrict
资格,因此程序也会因此而显示 UB。
I'm assuming memcpy
has a performance advantage since it copies data directly while memmove
uses a buffer. But with modern computers, should I disregard this potentially better performance and always use memmove
since it guarantees no overlap?
这是一个见仁见智的问题,因此这里离题了。我只想说,性能问题最好通过首先 测量 来解决,然后根据这些测量的结果做出决定。此外,不同实现的性能特征可能会有所不同。
restrict
永远不会停止未定义的行为。事实上,它在某些情况下引入了未定义的行为。如果一段没有UB的代码去掉restrict
,那么这段代码还是没有UB;但反之则不然。
您的代码在此行导致未定义的行为:
strcpy(itself, "Does restrict stop undefined behavior?");
由于已分配缓冲区的大小溢出。在那之后,所有赌注都取消了。
restrict
限定符不能防止缓冲区溢出。
我想看看 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);
}
输出()
Address of itself: 12345
Does restrict stop undefined behavior?
Address of itself: 12345
stop undefined bop undefined behavior?
Address of itself: 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
。如果不这样做,请使用 memmove
。 memmove
通常会检查是否存在重叠,如果没有重叠则最终使用 memcpy
,但您需要为检查付费。
The
memcpy
function copies n bytes from memory area src to memory area dest directly.
"directly" 我想你的意思是该函数避免先从源复制到缓冲区,然后再从缓冲区复制到目标。虽然这很可能是真的,但标准并不要求它是真的。
memmove
uses a buffer so there is no risk of overlapping memory.
不,memmove
产生结果 就好像 它首先复制到缓冲区,然后从那里复制到目的地。不需要以这种方式实际使用缓冲区,只要它产生所需的结果即可。任何给定的实现可能会或可能不会这样做。
Does
memcpy
use an independent pointer? Because the output definitely shows undefined behavior andrestrict
doesn't prevent access to overlapping memory withmemcpy
.
restrict
永远不会 阻止 任何事情。编译器不需要诊断,甚至不需要注意到您将别名指针传递给 restrict
限定的参数。事实上,这种决定通常根本无法在编译时做出。
事实上,当您使用第一次调用时使用的参数调用 memcpy()
时,您确实提供了两个独立的指针,指向与源参数和目标参数相同的对象。因此,程序的行为是未定义的。
由于计算 itself - 14
,您的程序还表现出未定义的行为(无论结果指针是否曾经被解除引用)。如果 itself
指向分配对象内部至少 14 个字节,这样指针算法是有效的,那么第二个 memcpy()
调用的参数将再次与参数的 restrict
资格,因此程序也会因此而显示 UB。
I'm assuming
memcpy
has a performance advantage since it copies data directly whilememmove
uses a buffer. But with modern computers, should I disregard this potentially better performance and always usememmove
since it guarantees no overlap?
这是一个见仁见智的问题,因此这里离题了。我只想说,性能问题最好通过首先 测量 来解决,然后根据这些测量的结果做出决定。此外,不同实现的性能特征可能会有所不同。
restrict
永远不会停止未定义的行为。事实上,它在某些情况下引入了未定义的行为。如果一段没有UB的代码去掉restrict
,那么这段代码还是没有UB;但反之则不然。
您的代码在此行导致未定义的行为:
strcpy(itself, "Does restrict stop undefined behavior?");
由于已分配缓冲区的大小溢出。在那之后,所有赌注都取消了。
restrict
限定符不能防止缓冲区溢出。