指针减法,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 将具有值0x7FD5395CCPSR寄存器的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,负距离报告为正的主要内容,如果未能解决你的问题,请参考以下文章

识别差异的最佳方法:欧几里得距离、余弦距离还是简单减法?

arm 霓虹灯比较操作产生负一

二进制用补码做加减法怎么实现,举个例子??

请教excel的坐标距离计算公式。

经纬度计算距离公式

远距离无线网桥LigoWave 5-N Rapidfire链路测算及测试报告