不同类型指针之间的减法
Subtraction between pointers of different type
我正在尝试查找内存中两个变量之间的距离。具体来说,我需要找到 char[] 数组和 int.
之间的距离
char data[5];
int a = 0;
printf("%p\n%p\n", &data[5], &a);
long int distance = &a - &data[5];
printf("%ld\n", distance);
当我 运行 我的程序没有最后两行时,我得到了两个变量的正确内存地址,如下所示:
0x7fff5661aac7
0x7fff5661aacc
现在我明白了,如果我没记错的话,两者之间有5个字节的距离(0x7fff5661aac8、0x7fff5661aac9、0x7fff5661aaca、0x7fff5661aacb、0x7fff5661aacc)。
为什么我不能减去一个 (int *) 类型的指针和一个 (char *) 类型的指针。两者都指的是内存地址。我应该怎么做才能计算两者之间的距离(以字节为单位)?我尝试投射两个指针之一,但它不起作用。
我得到:“错误:'char *' 和 'int *' 不是指向兼容类型的指针 ”。感谢大家会帮助我
不,这不可能。
首先,你只能减去(到)"compatible"类型的指针,一个int
和一个char
是不兼容的类型这里。因此减法是不可能的。
就是说,即使两者都是指向兼容类型的指针,也会出现以下情况。
所以,其次你不能只减去两个任意指针,它们必须是同一个数组(元素的地址)的本质部分。否则,它会调用 undefined behavior。
引用 C11
,章节 §6.5.6,加法运算符
When two pointers are subtracted, both shall point to elements of the same array object,
or one past the last element of the array object; the result is the difference of the
subscripts of the two array elements. [....]
第三,还有一个重点,两个指针相减的结果是ptrdiff_t
类型,有符号整数类型。
[...] The size of the result is implementation-defined,
and its type (a signed integer type) is ptrdiff_t
defined in the <stddef.h>
header. [...]
因此,要打印结果,您需要使用 %td
格式说明符。
在标准 PC 上,没有什么可以阻止您将两个指针都转换为可以保存指针值的整数类型,然后将这两个整数相减。
这样的整数类型并不能保证在所有体系结构上都存在(但在许多常见系统上确实存在)——想象一下分段内存具有比单个数字更多的信息。如果整数类型不适合,则转换行为未定义。
来自标准草案n1570, 6.3.2.3/6:
Any pointer type may be converted to an integer type. Except as
previously specified, the result is implementation-defined. If the
result cannot be represented in the integer type, the behavior is
undefined. The result need not be in the range of values of any
integer type.
通常地址之间的差异将是人们所期望的(连续声明的变量在内存中彼此相邻)并且可以用来告诉堆栈增长的方向等。
探索您可以用整数和指针做些什么可能会很有趣。
Olaf 评论说如果你 "cast [an arithmetic computation result] back to a pointer, you invoke UB." 那未必如此;这取决于整数值。标准草案在 6.3.2.3/5 中这样说:
An integer may be converted to any pointer type. Except as previously
specified, the result is implementation-defined, might not be
correctly aligned, might not point to an entity of the referenced
type, and might be a trap representation
(我强调。)如果我们通过向结构地址添加偏移量来计算结构成员的地址,我们显然已经解决了上述问题,所以这取决于实现。这当然不是 UB;如果我们不能使用整数 -> 指针转换并通过生成的指针访问该内存,那么很多嵌入式系统都会失败。我们必须确保系统允许,并且地址正确。
段落有脚注:
The mapping functions for converting a pointer to an integer or an integer to a pointer are intended to
be consistent with the addressing structure of the execution environment.
也就是说,它们是为了不要让用户感到惊讶。虽然理论上可以将内存中相邻的不相关对象的地址投影为截然不同的整数值,但它们不应该这样做。例如,用户可以合理地期望线性记忆被投影到线性整数 space,保持顺序和距离。
我还应该强调(正如我在一条评论中所做的那样)标准不是世界。它必须适应各种机器并为其提供保证。因此标准只能是最小公分母。如果我们能缩小我们考虑的架构范围,我们就能做出更好的保证。
一个常见的例子是整数寄存器中可能存在陷阱值,或者指示从未初始化的寄存器读取的标志,这也会陷阱;这些是标准中各种 UB 案例的原因,例如,它们根本不适用于您的 PC。
尝试将每个地址类型转换为 void *
long int distance = (void *)&a - (void *)&data[5];
正如其他人会指出的那样,这是危险且未定义的,但如果您只是探索内存的工作原理,那应该没问题。
这是因为指针运算是关于偏移量的。
例如,如果您有一个数组和一个指向该数组的指针,例如:
int array[3] = { 1, 2, 3};
int *ptr = array;
然后你递增 ptr,你期望数组中的下一个值,例如array[1] 之后的 array[0],不管里面存储的是什么类型。因此,当您减去指针时,您不会得到例如字节,但偏移量。
不要减去不属于同一数组的指针。
int 的大小和 char 指针的大小是 different.In 一个 int 大小为 4 字节的系统,如果你愿意的话 int_pointer++ 它会增加 4 个字节的地址,同时它会递增在 char_ptr 的情况下,地址增加 1 个字节。因此,您可能会遇到错误。
指针减法仅针对同一数组内的指针(或刚好超过数组的最后一个元素)定义。任何其他用途都是未定义的行为。为了您的实验,让我们忽略它。
当指向同一个数组对象的元素的两个相同类型的指针相减时,结果是数组索引的差值。您可以将该有符号整数结果(ptrdiff_t
类型)添加到第一个指针并获得第二个指针的值,或者从第二个指针中减去结果并获得第一个指针的值。所以实际上,结果是两个指针的字节地址之差除以所指向对象的大小。这就是为什么允许减去不兼容类型的指针没有意义,尤其是当引用的对象类型大小不同时。当减去的指针指向不同大小的对象时,如何将字节地址的差异除以所指向的对象的大小?
不过,出于实验目的,您可以将两个指针(指向不同的对象)都转换为 char *
并减去它们,许多编译器只会将它们的字节地址差值作为数字提供给您。但是,结果可能会溢出 ptrdiff_t
的整数。或者,您可以将两个指针都转换为 intptr_t
类型的整数,然后减去这些整数以获得字节地址的差异。同样,从理论上讲,减法的结果可能会溢出 intptr_t
.
类型的整数
uint8_t * ptr = ...;
uint8_t * ptr2 = ptr + 5;
现在如果 ptr
是 100
,ptr2
会是什么?正确,它将是 105
。但是现在看看那个代码:
uint32_t * ptr = ...;
uint32_t * ptr2 = ptr + 5;
同样,如果 ptr
是 100
,那么 ptr2
会是什么?错误的!不会是 105
,而是 120
.
为什么?指针运算不是整数运算!
ptr2 = ptr + 5;
实际意思是:
ptr2 = int_to_ptr(ptr_to_int(ptr) + (sizeof(ptr) * 5));
函数 int_to_ptr
和 ptr_to_int
并不真正存在,我只是将它们用于演示目的,以便您更好地了解场景之间发生的事情。
所以如果你减去两个指针,结果不是它们地址的差异,而是它们之间元素的数量:
uint32_t test[50];
ptrdiff_t diff = &test[20] - &test[10];
diff
将是 10,因为它们之间有 10 个元素(一个元素是一个 uint32_t
值)但这并不意味着 test[10]
之间有 10 个字节和 test[20]
,它们之间有 40 个字节,因为每个 uint32_t
值占用 4 个字节的内存。
现在你应该明白为什么不同类型的指针相减没有意义了,因为不同的类型有不同的元素大小,那么这样的相减是什么return?
如果你想要两个指针之间有多少字节,你需要将它们都转换为具有单字节元素的数据类型(例如 uint8_t *
或 char *
可以)或将它们转换为 void *
(GNU 扩展,但许多编译器也支持),这意味着数据类型未知,因此元素大小也未知,在这种情况下,编译器将字节大小的元素。所以这可能有效:
ptrdiff_t diff = (void *)ptr2 - (void *)ptr1;
然而这个
ptrdiff_t diff = (char *)ptr2 - (char *)ptr1;
更便携。
它会编译,它会提供一个结果。如果该结果有意义,那就是另一个话题了。除非两个指针都指向相同的内存 "object"(相同的结构、相同的数组、相同的分配内存区域),否则不会像标准所说的那样,在这种情况下,结果是未定义的。这意味着 diff
可以(合法地)具有任何值,因此在这种情况下,编译器也可以始终将 diff
设置为 0,这是标准所允许的。
如果你想要定义的行为,试试这个:
ptrdiff_t diff = (ptrdiff_t)ptr2 - (ptrdiff_t)ptr1;
这是合法且明确的。每个指针都可以转换为一个 int 值,并且 ptrdiff_t
是一个 int 值,保证足够大以便每个指针都可以放入它(永远不要使用 int
或 long
为此目的,他们不做任何此类保证!)。此代码将两个指针都转换为整数,然后将它们相减。我仍然没有看到您现在可以使用 diff
做任何有用的事情,但该代码至少会提供一个定义的结果,但可能不是您期望的结果。
我正在尝试查找内存中两个变量之间的距离。具体来说,我需要找到 char[] 数组和 int.
之间的距离 char data[5];
int a = 0;
printf("%p\n%p\n", &data[5], &a);
long int distance = &a - &data[5];
printf("%ld\n", distance);
当我 运行 我的程序没有最后两行时,我得到了两个变量的正确内存地址,如下所示:
0x7fff5661aac7
0x7fff5661aacc
现在我明白了,如果我没记错的话,两者之间有5个字节的距离(0x7fff5661aac8、0x7fff5661aac9、0x7fff5661aaca、0x7fff5661aacb、0x7fff5661aacc)。
为什么我不能减去一个 (int *) 类型的指针和一个 (char *) 类型的指针。两者都指的是内存地址。我应该怎么做才能计算两者之间的距离(以字节为单位)?我尝试投射两个指针之一,但它不起作用。
我得到:“错误:'char *' 和 'int *' 不是指向兼容类型的指针 ”。感谢大家会帮助我
不,这不可能。
首先,你只能减去(到)"compatible"类型的指针,一个int
和一个char
是不兼容的类型这里。因此减法是不可能的。
就是说,即使两者都是指向兼容类型的指针,也会出现以下情况。
所以,其次你不能只减去两个任意指针,它们必须是同一个数组(元素的地址)的本质部分。否则,它会调用 undefined behavior。
引用 C11
,章节 §6.5.6,加法运算符
When two pointers are subtracted, both shall point to elements of the same array object, or one past the last element of the array object; the result is the difference of the subscripts of the two array elements. [....]
第三,还有一个重点,两个指针相减的结果是ptrdiff_t
类型,有符号整数类型。
[...] The size of the result is implementation-defined, and its type (a signed integer type) is
ptrdiff_t
defined in the<stddef.h>
header. [...]
因此,要打印结果,您需要使用 %td
格式说明符。
在标准 PC 上,没有什么可以阻止您将两个指针都转换为可以保存指针值的整数类型,然后将这两个整数相减。
这样的整数类型并不能保证在所有体系结构上都存在(但在许多常见系统上确实存在)——想象一下分段内存具有比单个数字更多的信息。如果整数类型不适合,则转换行为未定义。
来自标准草案n1570, 6.3.2.3/6:
Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type.
通常地址之间的差异将是人们所期望的(连续声明的变量在内存中彼此相邻)并且可以用来告诉堆栈增长的方向等。
探索您可以用整数和指针做些什么可能会很有趣。
Olaf 评论说如果你 "cast [an arithmetic computation result] back to a pointer, you invoke UB." 那未必如此;这取决于整数值。标准草案在 6.3.2.3/5 中这样说:
An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation
(我强调。)如果我们通过向结构地址添加偏移量来计算结构成员的地址,我们显然已经解决了上述问题,所以这取决于实现。这当然不是 UB;如果我们不能使用整数 -> 指针转换并通过生成的指针访问该内存,那么很多嵌入式系统都会失败。我们必须确保系统允许,并且地址正确。
段落有脚注:
The mapping functions for converting a pointer to an integer or an integer to a pointer are intended to be consistent with the addressing structure of the execution environment.
也就是说,它们是为了不要让用户感到惊讶。虽然理论上可以将内存中相邻的不相关对象的地址投影为截然不同的整数值,但它们不应该这样做。例如,用户可以合理地期望线性记忆被投影到线性整数 space,保持顺序和距离。
我还应该强调(正如我在一条评论中所做的那样)标准不是世界。它必须适应各种机器并为其提供保证。因此标准只能是最小公分母。如果我们能缩小我们考虑的架构范围,我们就能做出更好的保证。
一个常见的例子是整数寄存器中可能存在陷阱值,或者指示从未初始化的寄存器读取的标志,这也会陷阱;这些是标准中各种 UB 案例的原因,例如,它们根本不适用于您的 PC。
尝试将每个地址类型转换为 void *
long int distance = (void *)&a - (void *)&data[5];
正如其他人会指出的那样,这是危险且未定义的,但如果您只是探索内存的工作原理,那应该没问题。
这是因为指针运算是关于偏移量的。 例如,如果您有一个数组和一个指向该数组的指针,例如:
int array[3] = { 1, 2, 3};
int *ptr = array;
然后你递增 ptr,你期望数组中的下一个值,例如array[1] 之后的 array[0],不管里面存储的是什么类型。因此,当您减去指针时,您不会得到例如字节,但偏移量。
不要减去不属于同一数组的指针。
int 的大小和 char 指针的大小是 different.In 一个 int 大小为 4 字节的系统,如果你愿意的话 int_pointer++ 它会增加 4 个字节的地址,同时它会递增在 char_ptr 的情况下,地址增加 1 个字节。因此,您可能会遇到错误。
指针减法仅针对同一数组内的指针(或刚好超过数组的最后一个元素)定义。任何其他用途都是未定义的行为。为了您的实验,让我们忽略它。
当指向同一个数组对象的元素的两个相同类型的指针相减时,结果是数组索引的差值。您可以将该有符号整数结果(ptrdiff_t
类型)添加到第一个指针并获得第二个指针的值,或者从第二个指针中减去结果并获得第一个指针的值。所以实际上,结果是两个指针的字节地址之差除以所指向对象的大小。这就是为什么允许减去不兼容类型的指针没有意义,尤其是当引用的对象类型大小不同时。当减去的指针指向不同大小的对象时,如何将字节地址的差异除以所指向的对象的大小?
不过,出于实验目的,您可以将两个指针(指向不同的对象)都转换为 char *
并减去它们,许多编译器只会将它们的字节地址差值作为数字提供给您。但是,结果可能会溢出 ptrdiff_t
的整数。或者,您可以将两个指针都转换为 intptr_t
类型的整数,然后减去这些整数以获得字节地址的差异。同样,从理论上讲,减法的结果可能会溢出 intptr_t
.
uint8_t * ptr = ...;
uint8_t * ptr2 = ptr + 5;
现在如果 ptr
是 100
,ptr2
会是什么?正确,它将是 105
。但是现在看看那个代码:
uint32_t * ptr = ...;
uint32_t * ptr2 = ptr + 5;
同样,如果 ptr
是 100
,那么 ptr2
会是什么?错误的!不会是 105
,而是 120
.
为什么?指针运算不是整数运算!
ptr2 = ptr + 5;
实际意思是:
ptr2 = int_to_ptr(ptr_to_int(ptr) + (sizeof(ptr) * 5));
函数 int_to_ptr
和 ptr_to_int
并不真正存在,我只是将它们用于演示目的,以便您更好地了解场景之间发生的事情。
所以如果你减去两个指针,结果不是它们地址的差异,而是它们之间元素的数量:
uint32_t test[50];
ptrdiff_t diff = &test[20] - &test[10];
diff
将是 10,因为它们之间有 10 个元素(一个元素是一个 uint32_t
值)但这并不意味着 test[10]
之间有 10 个字节和 test[20]
,它们之间有 40 个字节,因为每个 uint32_t
值占用 4 个字节的内存。
现在你应该明白为什么不同类型的指针相减没有意义了,因为不同的类型有不同的元素大小,那么这样的相减是什么return?
如果你想要两个指针之间有多少字节,你需要将它们都转换为具有单字节元素的数据类型(例如 uint8_t *
或 char *
可以)或将它们转换为 void *
(GNU 扩展,但许多编译器也支持),这意味着数据类型未知,因此元素大小也未知,在这种情况下,编译器将字节大小的元素。所以这可能有效:
ptrdiff_t diff = (void *)ptr2 - (void *)ptr1;
然而这个
ptrdiff_t diff = (char *)ptr2 - (char *)ptr1;
更便携。
它会编译,它会提供一个结果。如果该结果有意义,那就是另一个话题了。除非两个指针都指向相同的内存 "object"(相同的结构、相同的数组、相同的分配内存区域),否则不会像标准所说的那样,在这种情况下,结果是未定义的。这意味着 diff
可以(合法地)具有任何值,因此在这种情况下,编译器也可以始终将 diff
设置为 0,这是标准所允许的。
如果你想要定义的行为,试试这个:
ptrdiff_t diff = (ptrdiff_t)ptr2 - (ptrdiff_t)ptr1;
这是合法且明确的。每个指针都可以转换为一个 int 值,并且 ptrdiff_t
是一个 int 值,保证足够大以便每个指针都可以放入它(永远不要使用 int
或 long
为此目的,他们不做任何此类保证!)。此代码将两个指针都转换为整数,然后将它们相减。我仍然没有看到您现在可以使用 diff
做任何有用的事情,但该代码至少会提供一个定义的结果,但可能不是您期望的结果。