指针减法,32 位 ARM,负距离报告为正
Posted
技术标签:
【中文标题】指针减法,32 位 ARM,负距离报告为正【英文标题】:Pointer subtraction, 32-bit ARM, negative distance reported as postive 【发布时间】:2017-02-17 17:48:57 【问题描述】:当执行指针减法并且第一个指针小于第二个时,ARM 处理器出现下溢错误。
示例代码:
#include <stdint.h>
#include <stdbool.h>
uint8_t * p_formatted_data_end;
uint8_t formatted_text_buffer[10240];
static _Bool
Flush_Buffer_No_Checksum(void)
_Bool system_failure_occurred = false;
p_formatted_data_end = 0; // For demonstration puposes.
const signed int length =
p_formatted_data_end - &formatted_text_buffer[0];
if (length < 0)
system_failure_occurred = true;
//...
return true;
IAR编译器生成的汇编代码为:
807 static _Bool
808 Flush_Buffer_No_Checksum(void)
809
\ Flush_Buffer_No_Checksum:
\ 00000000 0xE92D4070 PUSH R4-R6,LR
\ 00000004 0xE24DD008 SUB SP,SP,#+8
810 _Bool system_failure_occurred = false;
\ 00000008 0xE3A04000 MOV R4,#+0
811 p_formatted_data_end = 0; // For demonstration purposes.
\ 0000000C 0xE3A00000 MOV R0,#+0
\ 00000010 0x........ LDR R1,??DataTable3_7
\ 00000014 0xE5810000 STR R0,[R1, #+0]
812 const signed int length =
813 p_formatted_data_end - &formatted_text_buffer[0];
\ 00000018 0x........ LDR R0,??DataTable3_7
\ 0000001C 0xE5900000 LDR R0,[R0, #+0]
\ 00000020 0x........ LDR R1,??DataTable7_7
\ 00000024 0xE0505001 SUBS R5,R0,R1
814 if (length < 0)
\ 00000028 0xE3550000 CMP R5,#+0
\ 0000002C 0x5A000009 BPL ??Flush_Buffer_No_Checksum_0
815
816 system_failure_occurred = true;
\ 00000030 0xE3A00001 MOV R0,#+1
\ 00000034 0xE1B04000 MOVS R4,R0
减法指令SUBS R5,R0,R1
等价于:
R5 = R0 - R1
如果结果是否定的,CPSR
寄存器中的N
位将被设置。
参考:ARM Architecture Reference Manual 的第 A4.1.106 节 SUB
让:
R0 == 0x00000000
R1 == 0x802AC6A5
注册R5
将具有值0x7FD5395C
。
CPSR
寄存器的N
位为0,表示结果不是负数。
Windows 7 计算器应用程序报告负数,但仅在以 64 位表示时:FFFFFFFF7FD5395C
。
作为一个实验,我使用ptrdiff_t
类型作为长度,生成了相同的汇编语言。
问题:
-
这是有效的行为吗,将指针减到
下溢?
将距离视为负数的推荐数据类型是什么?
平台: 目标处理器:ARM Cortex A8 (TI AM3358) 编译器:IAR 7.40 开发平台:Windows 7。
【问题讨论】:
p_formatted_data_end = 0;
显然没有指向数组formatted_text_buffer
(或之后的字节)中的任何位置。不相关实体上的指针算法是未定义的行为。请注意,处理器标志寄存器中的符号位反映了寄存器或操作的 ms 位,无论您认为该值是无符号还是有符号。
但是阴性结果不是很可行吗?如果从指向formatted_text_buffer[0]
(8 位数组)的指针中减去指向formatted_text_buffer[3]
的指针,得到的-3
会告诉您它们之间有多少个元素。
两个不指向同一个数组的指针之间的算术是UB。
你取一个负数,然后从零中减去它,得到一个正数。哪个是正确的,这有什么问题? 0 - (-5) = +5。小学数学。现在下溢是什么意思?这个数学既不会产生无符号溢出(C),也不会产生有符号溢出(V),正如您所指出的,N 为零,Z 为零。
这些都与 ARM 无关,只是简单的数学运算。
【参考方案1】:
这是有效的行为,有指针减法到下溢的结果吗?
是的,因为您的情况下的行为是未定义的。 任何 行为在那里都是有效的。正如在 cmets 中观察到的,两个指针之间的差异仅针对指向同一数组对象的元素的指针定义,或者指向数组对象的最后一个元素的指针(C2011,6.5.6/9)。
将距离视为负数的推荐数据类型是什么?
在它被定义的地方,两个指针相减的结果被指定为ptrdiff_t
类型,一个实现定义大小的有符号整数类型。如果计算p1 - p2
,其中p1
指向一个数组元素,p2
指向同一数组的后一个元素,则结果将是一个负数,可表示为ptrdiff_t
。
【讨论】:
当我和一位同事讨论这个问题时,没有足够的位。如果我有一个巨大的数组,那么我需要 32 位的绝对差和一个额外的符号位。 IAR 编译器的 ptrdiff_t 类型仅使用 32 位,这还不够。由于使用了 32 位,因此最大数组大小为 31 位,以便为符号保留一位(当 ptrdiff_t 类型使用 32 位时)。 @ThomasMatthews,要么ptrdiff_t
有足够的位来表示其行为已定义的所有指针差异操作的结果,要么实现是不合格。在实践中,(符合)实现为ptrdiff_t
选择一个大小,因为它们支持的最大数组大小。如果您的实现实际上不符合,那么我们没有任何基础来回答这个问题;否则,我给出的答案适用。
@ThomasMatthews,或者,换个角度来看,该标准还规定“如果 [指针差异的结果] 在 [ptrdiff_t
] 类型的对象中不可表示,则行为是未定义的。”如果ptrdiff_t
的宽度不足以表示所有指针差异,否则这些差异将具有定义的行为,这会导致其中一些差异未定义。在适用的情况下,没有理由假设有任何方法可以将差值作为负数。【参考方案2】:
虽然这是另一个答案中所述的 UB,但大多数 C 实现无论如何都会简单地减去这些指针 ptrdiff_t
大小(或者可能使用适当的算术来计算它们的字长,如果两个操作数都是 near
/ far
/huge
指针)。结果应该适合ptrdiff_t
,在ARM上通常是typedef-ed int
:
typedef int ptrdiff_t;
因此,在这种特殊情况下,您的代码的问题只是您将 unsigned int
值视为有符号,而它不适合。正如您在问题中指定的那样,formatted_text_buffer
的地址是0x802AC6A5
,它适合unsigned int
,但(int)0x802AC6A5
在二进制补码形式中实际上是一个负 数(-0x7FD5395B
)。所以从0
中减去一个负数将返回一个正数int
,正如预期的那样。
如果两个操作数之间的距离小于0x7FFFFFFF
,带符号的 32 位整数减法将正常工作,并且期望您的数组小于该值是合理的:
// this will work
const int length = &formatted_text_buffer[0] - &formatted_text_buffer[100];
或者,如果您确实需要减去不适合有符号 32 位整数的指针,请改用 long long
:
// ...but I doubt you really want this
const long long length = (long long)p_formatted_data_end -
(long long)&formatted_text_buffer[0];
【讨论】:
以上是关于指针减法,32 位 ARM,负距离报告为正的主要内容,如果未能解决你的问题,请参考以下文章