2017-02-17 33 views
-1

При выполнении вычитания указателей и первого указателя меньше второго, я получаю ошибку underflow с процессором ARM.Вычитание указателя, 32-битное ARM, отрицательное расстояние, сообщенное как postive

Пример кода:

#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 

N бит регистр CPSR будет t, если результат отрицательный. Ссылка: Раздел A4.1.106 SUB из ARM Architecture Reference Manual

Пусть:

R0 == 0x00000000 
R1 == 0x802AC6A5 

Регистрация R5 будет иметь значение 0x7FD5395C. N бит регистра CPSR: 0, с указанием результата не является отрицательным.

Приложение «Калькулятор Windows 7» сообщает об отрицательном значении, но только в том случае, если оно выражается как 64-разрядные: FFFFFFFF7FD5395C.

В качестве эксперимента я использовал тип ptrdiff_t для длины, и был сгенерирован тот же язык ассемблера.

Вопросы:

  1. Является ли это действительным поведение, чтобы получить результат вычитания указателя на опустошения?
  2. Каков рекомендуемый тип данных для просмотра расстояния как отрицательный?

Платформа:
Target Процессор: ARM Cortex A8 (TI AM3358)
Компилятор: IAR 7,40
Развитие платформы: Windows 7.

+5

'p_formatted_data_end = 0; ', очевидно, не указывает на любом месте в пределах массива' formatted_text_buffer' (или байт после). Арифметика указателя на несвязанные объекты - это неопределенное поведение *. Обратите внимание, что бит знака в регистре флагов процессора отражает бит ms регистра или операции независимо от того, считаете ли вы значение без знака или подписью. –

+1

Но не является ли вообще отрицательным результатом? Если вы вычтите указатель на 'formatted_text_buffer [3]' из указателя на 'formatted_text_buffer [0]' (8-битный массив), то итоговый '-3' сообщает вам, сколько элементов они находятся отдельно. –

+6

Арифметика между двумя указателями, не указывающими на один и тот же массив, - UB. – Fredrik

ответ

2

Является ли это действительным поведение, чтобы иметь результат вычитания указателя на нижний поток?

Да, поскольку поведение в вашем случае не определено. Любое поведение действительно. Как отмечалось в комментариях, разница между двумя указателями определяется только для указателей, которые указывают на элементы одного и того же объекта массива, или один за последним элементом объекта массива (C2011, 6.5.6/9).

Каков рекомендуемый тип данных для просмотра расстояния как негативный?

В случае определения результата вычитания двух указателей указывается тип ptrdiff_t, знаковый целочисленный тип определенного для реализации размера. Если вы оцениваете p1 - p2, где p1 указывает на элемент массива, а p2 указывает на более поздний элемент того же массива, тогда результат будет отрицательным числом, представляемым как ptrdiff_t.

+0

Как я обсуждал это с коллегой, битков не хватает. Если у меня есть огромный массив, тогда мне понадобится 32 бита для абсолютной разницы и дополнительный бит для знака. Компилятор IAR использует только 32 бита для своего типа ptrdiff_t, чего недостаточно. Из-за использования 32 бит максимальный размер массива будет 31 бит, чтобы зарезервировать один бит для знака (при использовании 32 бит для типа ptrdiff_t). –

+0

@ThomasMatthews, либо 'ptrdiff_t' имеет достаточно бит, чтобы представлять результаты всех операций разности указателей ***, поведение которых определено ***, или реализация не соответствует требованиям. На практике (соответствующие) реализации выбирают размер для 'ptrdiff_t' с учетом наибольшего размера массива, который они поддерживают. Если ваша реализация на самом деле не соответствует, то у нас нет никаких оснований для ответа на вопрос; в противном случае ответ, который я дал, применяется. –

+0

@ThomasMatthews, или, глядя на него по-другому, стандарт также указывает, что «Если результат [разности указателей] не является представимым в объекте [type' ptrdiff_t', поведение не определено ». Если 'ptrdiff_t' недостаточно широк, чтобы представлять все различия указателей, которые иначе определяли бы поведение, это служит для того, чтобы некоторые из этих различий были неопределенными. Там, где это применимо, нет оснований полагать, что есть какой-либо способ получить разницу в качестве отрицательного числа. –

1

Хотя это UB, как указано в другом ответе, большинство реализаций C будет просто вычитать эти указатели в любом случае ptrdiff_t размер (или, возможно, с использованием соответствующих арифметических операций для их размера слова, которые также могут быть различными, если оба операнда near/far/huge указатели). Результат должен соответствовать внутри ptrdiff_t, который, как правило, typedef-ed int на ARM:

typedef int ptrdiff_t; 

Так что вопрос с вашим кодом в данном случае будет просто, что вы лечение unsigned int значения как знаковое, и это не поместиться. Как указано в вашем вопросе, адрес formatted_text_buffer - 0x802AC6A5, который вписывается в номер unsigned int, но (int)0x802AC6A5 в форме дополнения двух на самом деле является номером отрицательным номером (-0x7FD5395B). Поэтому вычитание отрицательного числа из 0 вернет положительное int, как и ожидалось.

Подпись 32-разрядное целое число Вычитание будет работать корректно, если оба операнда меньше, чем 0x7FFFFFFF друг от друга, и это разумно ожидать, что ваши массивы быть меньше, чем:

// 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];