在对这个最近问题的评论中出现了一个有趣的讨论:现在,虽然那里的语言是C,但讨论已经转向C++标准所指定的内容,即使用 a 访问多维数组的元素时构成未定义行为的内容。功能类似于std::memcpy
.
首先,这是该问题的代码,已转换为 C++ 并const
尽可能使用:
#include <iostream>\n#include <cstring>\n\nvoid print(const int arr[][3], int n)\n{\n for (int r = 0; r < 3; ++r) {\n for (int c = 0; c < n; ++c) {\n std::cout << arr[r][c] << " ";\n }\n std::cout << std::endl;\n }\n}\n\nint main()\n{\n const int arr[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };\n int arr_copy[3][3];\n print(arr, 3);\n std::memcpy(arr_copy, arr, sizeof arr);\n …
Run Code Online (Sandbox Code Playgroud) c c++ multidimensional-array undefined-behavior language-lawyer
考虑这个代码:
#include <functional>
#include <typeinfo>
template <typename T>
inline constexpr const void *foo = &typeid(T);
int main()
{
constexpr bool a = std::less<const void*>{}(foo<int>, foo<float>);
}
Run Code Online (Sandbox Code Playgroud)
如果我使用<
而不是std::less
这里,代码不会编译。这并不奇怪,因为如果指针指向不相关的对象,则关系指针比较的结果是未指定的,而且显然这样的比较不能在编译时完成。
Run Code Online (Sandbox Code Playgroud)#include <functional> #include <typeinfo> template <typename T> inline constexpr const void *foo = &typeid(T); int main() { constexpr bool a = std::less<const void*>{}(foo<int>, foo<float>); }
即使我使用std::less
. 编译器错误是一样的。std::less
似乎<
至少在 libstdc++ 和 libc++ 中实现;我在 GCC、Clang 和 MSVC 上得到了相同的结果。
但是,关于 cppreference 的页面 …
在 K&R(C 编程语言第 2 版)第 5 章中,我阅读了以下内容:
首先,在某些情况下可以比较指针。如果
p
和q
指向同一个数组的成员,则关系一样==
,!=
,<
,>=
,等正常工作。
这似乎意味着只能比较指向同一数组的指针。
但是,当我尝试此代码时
char t = 't';
char *pt = &t;
char x = 'x';
char *px = &x;
printf("%d\n", pt > px);
Run Code Online (Sandbox Code Playgroud)
1
被打印到屏幕上。
首先,我认为我会得到 undefined 或某种类型或错误,因为pt
和px
没有指向同一个数组(至少在我的理解中)。
也是pt > px
因为两个指针都指向存储在栈上的变量,栈向下增长,所以内存地址t
大于x
?为什么pt > px
是真的?
当 malloc 被引入时,我变得更加困惑。 同样在第 8.7 章的 K&R 中写到以下内容:
然而,仍然存在一种假设,即
sbrk
可以有意义地比较指向由 返回的不同块的指针。仅允许在数组内进行指针比较的标准并不能保证这一点。因此,这个版本的malloc
仅在一般指针比较有意义的机器之间是可移植的。
将指向堆上 malloced …
从我系统的手册页:
void*memmove(void*dst,const void*src,size_t len);
描述
memmove()函数将字符串src中的len个字节复制到字符串dst.
两个字符串可能重叠 ; 副本总是以非破坏性的
方式完成.
从C99标准:
6.5.8.5比较两个指针时,结果取决于指向的对象的地址空间中的相对位置.如果指向对象或不完整类型的两个指针都指向同一个对象,或者两个指针都指向同一个数组对象的最后一个元素,则它们相等.如果指向的对象是同一聚合对象的成员,则指向稍后声明的结构成员的指针比指向结构中先前声明的成员的指针大,指向具有较大下标值的数组元素的指针比指向同一数组的元素的指针大.具有较低的下标值.指向同一个union对象的成员的所有指针都比较相等.如果表达
P
指向数组对象的元素,表达式Q指向同一数组对象的最后一个元素,指针表达式Q+1
比较大于P
.在所有其他情况下,行为 未定义.
重点是我的.
的参数dst
和src
可被转化为指针char
以便减轻严格别名的问题,但有可能以比较两个指针可以指向内部的不同的块,以便做以正确的顺序的拷贝的情况下,它们指向相同的块内?
显而易见的解决方案是if (src < dst)
,但未定义如果src
和dst
指向不同的块."未定义"意味着您甚至不应该假设条件返回0或1(这在标准词汇表中称为"未指定").
另一种选择是if ((uintptr_t)src < (uintptr_t)dst)
,至少是未指定的,但我不确定标准是否保证何时src < dst
定义,它等同于(uintptr_t)src < (uintptr_t)dst)
.指针比较是从指针算法定义的.例如,当我在添加时阅读第6.5.6节时,在我看来,指针算法可以与uintptr_t
算术相反的方向,即兼容的编译器可能具有,当p
类型为char*
:
((uintptr_t)p)+1==((uintptr_t)(p-1)
Run Code Online (Sandbox Code Playgroud)
这只是一个例子.一般来说,将指针转换为整数时,似乎可以保证很少.
这是一个纯粹的学术问题,因为memmove
它与编译器一起提供.在实践中,编译器作者可以简单地将未定义的指针比较提升为未指定的行为,或者使用相关的编译指示强制其编译器memmove
正确编译它们.例如,此实现具有以下代码段:
if ((uintptr_t)dst < (uintptr_t)src) {
/*
* As author/maintainer of libc, take advantage …
Run Code Online (Sandbox Code Playgroud) 请考虑以下代码:
int a[25][80];
a[0][1234] = 56;
int* p = &a[0][0];
p[1234] = 56;
Run Code Online (Sandbox Code Playgroud)
第二行是否调用未定义的行为?第四行怎么样?
c++ arrays pointers multidimensional-array undefined-behavior
假设我有这段代码,可以根据它们的位置以一定的顺序将一个内存块复制到另一个内存中:
void *my_memmove(void *dest, const void *src, size_t len)
{
const unsigned char *s = (const unsigned char *)src;
unsigned char *d = (unsigned char *)dest;
if(dest < src)
{
/* copy s to d forwards */
}
else
{
/* copy s to d backwards */
}
return dest;
}
Run Code Online (Sandbox Code Playgroud)
如果src
并且dest
不指向同一数组(6.8.5p5)的成员,则这是未定义的行为。
但是,假设我将这两个指针转换为uintptr_t
类型:
#include <stdint.h>
void *my_memmove(void *dest, const void *src, size_t len)
{
const unsigned char *s = (const …
Run Code Online (Sandbox Code Playgroud) 在编写x86-64用户空间程序集并比较两个指针值时,我们应该使用带符号的条件(例如jl
和)jge
还是使用无符号的条件(例如jb
和)jae
?
直觉上,我认为指针是无符号的,在64位进程的情况下,指针从0到2 ^ 64-1,并且我认为该模型对于32位代码是准确的。我想这就是大多数人对他们的看法。
但是,在64位代码中,我认为您无法有效地跨越0x7FFFFFFFFFFFFFFF
(2 ^ 63-1)处的有符号不连续性,并且许多有趣的内存区域倾向于聚集在有符号0附近(对于代码和静态数据,有时甚至是有时)堆的大小取决于实现),并且0x00007fffffffffff
在某些实现1的堆栈地址和堆附近接近规范地址空间下半部分的最大地址(类似于当今的大多数系统)。
因此,我不确定应该采用哪种方式对待它们:带符号的优点是它在0附近是安全的,因为那里没有间断;而无符号的优点是在2 ^ 63附近,因为那里没有间断。但是实际上,您不会在2 ^ 63附近看到任何地址,因为当前商用硬件的虚拟地址空间限制为小于50位。这是否指向签名?
1 ...,有时堆和其他映射区域不靠近地址空间的底部或顶部。
我想知道 void 指针是如何实现的。我试图用 x86-64 在 Godbolt 上找到它(你可以在这里看到它),但它没有透露任何信息。空指针是如何实现的?
编辑: 这是我使用的代码:
int main() {
int volatile y = 123;
void* volatile x = &y;
}
Run Code Online (Sandbox Code Playgroud)
我想在这里看到的只是它的x
样子。我放置了 volatile 以便 gcc 不会将其作为死代码消除。