指针减法,32 位 ARM,负距离报告为正

Pointer subtraction, 32-bit ARM, negative distance reported as postive

当执行指针减法并且第一个指针小于第二个时,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 子部分

设:

R0 == 0x00000000
R1 == 0x802AC6A5

注册 R5 将具有值 0x7FD5395CCPSR寄存器的N位为0,表示结果不为负

Windows 7 计算器应用程序报告负数,但仅当表示为 64 位时:FFFFFFFF7FD5395C.

作为实验,我使用 ptrdiff_t 类型作为长度,生成了相同的汇编语言。

问题:

  1. 指针减法的结果是否有效 下溢?
  2. 将距离视为负数的推荐数据类型是什么?

平台:
目标处理器:ARM Cortex A8 (TI AM3358)
编译器:IAR 7.40
开发平台:Windows7.

Is this valid behavior, to have the result of pointer subtraction to underflow?

是的,因为您的案例中的行为未定义。 任何 行为在那里都是有效的。正如评论中所观察到的,两个指针之间的差异仅针对指向同一数组对象元素的指针或指向数组对象最后一个元素的指针定义(C2011,6.5.6/9)。

What is the recommended data type to view the distance as negative?

定义的地方,指定两个指针相减的结果为ptrdiff_t类型,大小为implementation-defined的有符号整数类型。如果计算 p1 - p2,其中 p1 指向一个数组元素,而 p2 指向同一数组的后面的元素,那么结果将是一个负数,表示为 ptrdiff_t.

虽然这是另一个答案中所述的 UB,但大多数 C 实现将简单地减去这些指针 ptrdiff_t 大小(或者可能使用适当的算法来计算它们的字长,如果两个操作数都是near/far/huge 指针)。结果应该适合 ptrdiff_t, which is usually a typedef-ed int on ARM:

typedef int ptrdiff_t;

因此,在这种特殊情况下,您的代码存在的问题只是您将 unsigned int 值视为已签名,但它不适合。正如您在问题中指定的那样,formatted_text_buffer 的地址是 0x802AC6A5,它位于 unsigned int 内,但 (int)0x802AC6A5 的二进制补码形式实际上是 negative 数 (-0x7FD5395B)。因此,从 0 中减去一个负数将 return 正数 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];