Странное поведение указателей C с вводом-выводом с отображением памяти

В настоящее время я работаю над написанием базовой операционной системы в качестве учебного проекта. Для этой цели я использую кросс-компилятор gcc 4.9.2. При попытке использовать ввод-вывод с отображением памяти я наткнулся на поведение указателей C (или, возможно, ввод-вывод с отображением памяти), которое я не могу понять. При прямом доступе к памяти ввода-вывода с помощью следующего кода я получаю ожидаемый результат, который представляет собой «AB» в верхнем левом углу светло-серым шрифтом на черном фоне.

*((uint16_t *)0xB8000) = 0x0741;
*((uint16_t *)0xB8002) = 0x0742;

Однако при попытке манипулировать памятью с помощью смещения, добавляющего 2 к базовому адресу, результатом будет не «А» во второй, а в третьей позиции, как ожидалось.

*((uint16_t *)0xB8000 + 2) = 0x0741;

Добавление 1 вместо 2 выводит букву на вторую позицию, однако я не понимаю, почему. Поскольку каждая буква (в MMIO) состоит из 2 байтов данных, я бы предположил, что мне нужно увеличить адрес памяти, в который я пишу, на 2 для следующего символа. (Как я сделал сначала, написав напрямую в 0xB8002). Чтобы попытаться понять это поведение, я провел некоторое тестирование в отдельной программе на C, но не смог воспроизвести это поведение: (Примечание: этот код был скомпилирован с использованием обычного gcc 4.8.2. )

#include <stdint.h>
#include <stdio.h>

void main(void) {
        printf("0xB8000 + 1 = %x\n", 0xB8000 + 1);
        printf("&(*(uint16_t *)(0xB8000 + 1)) = %x\n", (uint32_t)&(*(uint16_t *)(0xB8000 + 1)));
}

Эта программа произвела следующий вывод:

0xB8000 + 1 = b8001
&(*(uint16_t *)(0xB8000 + 1)) = b8001

Я предполагаю, что мне не хватает какого-то поведения указателей C, которое вызывает факторизацию размера данных, которые записываются оператором. Так ли это или есть другая причина, которую я упустил из виду?

С уважением, Кеншуак.


person kenshooak    schedule 11.02.2015    source источник
comment
Вам может быть полезно перечитать основы арифметики указателей (всегда масштабируется по базовому типу). Или, может быть, приоритет оператора (приведение имеет более высокий приоритет, чем +).   -  person Deduplicator    schedule 11.02.2015
comment
Кроме того, вы можете использовать здесь volatile, чтобы убедиться, что компилятор не пытается быть умным и оптимизировать присваивания.   -  person Tim Čas    schedule 11.02.2015


Ответы (1)


Если вы добавите целое число n к указателю <type>, адрес увеличится на sizeof( <type> ) * n байт. Итак, в вашем первом примере у вас есть

*((uint16_t *)0xB8000 + 2) = 0x0741;

Итак, что здесь происходит: 0xB8000 приводится к unit16_t *, а затем этот указатель увеличивается на 2, что означает 2 * sizeof( uint16_t ) = 4 байта.

Во втором примере у вас есть

 *(uint16_t *)(0xB8000 + 1)

Обратите внимание на другие скобки: здесь вы сначала добавляете 1 к 0xB8000 — простая целочисленная операция — и приводите результат.

person Ingo Leonhardt    schedule 11.02.2015
comment
Большое спасибо, я совершенно упустил, что разный брекетинг может повлиять на результат! - person kenshooak; 11.02.2015