2016-07-21 1 views
4

Я пытаюсь написать эмулятор Gameboy в C, и в настоящее время находится в процессе принятия решения о том, как реализовать следующее поведение:Можно ли создать два указателя uint8_t для первой и второй половины значения, указанного указателем uint16_t?

  • Два 8-битные регистры могут быть объединены и рассматриваться как единый 16- битовый регистр
  • изменения значения одного из 8-битных регистров в паре должны изменить значение комбинированного регистра

Например, регистры а и F, которые являются 8-битными регистрами, может быть используется совместно как 16-разрядный регистр AF. Однако при изменении содержимого регистров A и F эти изменения должны быть отражены в последующих рефералах для регистрации AF.

Если я реализую регистр AF как uint16_t*, могу ли я сохранить содержимое регистров A и F как uint8_t*, указывая на первый и второй байты регистра AF соответственно? Если нет, то любые другие предложения будут оценены :)

EDIT: Просто чтобы прояснить, что это очень похоже на архитектуру Z80

+2

Попробуйте союз ... но обратите внимание на утверждение. – Dmitri

+0

он также похож на x86 –

ответ

3

Используйте объединение.

union b 
{ 
    uint8_t a[2]; 
    uint16_t b; 
}; 

Члены a и b разделяют байты. Когда значение записывается в член a и затем считывается с использованием члена b, значение интерпретируется в этом типе. Это может быть ловушечное представление, которое приведет к неопределенному поведению, но типы uint8_t и uint16_t их не имеют.

Другой проблема является порядком байт, записи в первый элемент члена a всегда будет изменить первые байты элемента b, но в зависимости от порядок байт, что байты могут представлять большинство или наименее значимых бит b, так что полученное значение будет отличаться над архитектуры.


Чтобы избежать ловушки представления и порядком байтов, а только использовать тип uint16_t и писать в нее с помощью битовые операции. Так, например, для записи в наиболее значимых 8 бит:

uint16_t a = 0; 
uint8_t b = 200; 
a = (uint16_t)(((unsigned int)a & 0xFF) | ((unsigned int)b << 8)) ; 

и аналогично для наименее значимых битов 8:

a = (uint16_t)(((unsigned int)a & 0xFF00) | (unsigned int)b); 

Эти операции должны быть введены в функцию.

Присваивается (unsigned int), чтобы избежать целых рекламных акций. Если INT_MAX равно 2^15-1, и существуют ловушечные представления для целых чисел со знаком, то операция: может технически вызывать неопределенное поведение.

+0

Означает ли это, что мне придется использовать: AF.A, AF.F, AF.AF, когда мне нужно получить доступ к регистрам по отдельности? И модифицирует ли одна часть часть целого? –

+0

@JoshThieme Он делает, но будьте осторожны. Я бы предложил вам использовать второй вариант. См. Обновление. – 2501

+0

Благодарим вас за подробный ответ. Однако я считаю, что из-за того, как работают некоторые инструкции cpu, a и b должны быть указателями на целые числа, а не целые. Может ли это быть адаптировано к этому случаю? –

-1

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

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

int main() { 
    uint16_t a=1048; 
    uint8_t *ah=(uint8_t*)&a,*al=((uint8_t*)&a)+1; 
    printf("%u,%u,%u\n",a,*ah,*al); 
} 

Вы должны были бы заботиться о качестве байтов упомянуто в комментарии ( Я считаю, что Gameboy мало младшему, в то время как x86 большой порядок байт).

И, конечно же, как большинство Poeple рекомендует вам следует использовать union, который сделает ваш код меньше ошибок из-за стрелочное вычисление адресов

0

Вы могли бы решить, как это:

volatile uint16_t afvalue = 0x55AA; // Needs long lifetime. 

volatile uint16_t *afptr = &afvalue; 
volatile uint8_t *aptr = (uint8_t *)afptr; 
volatile uint8_t *fptr = aptr + 1; 

if (*aptr == 0xAA) {   // Take endianness into account. 
    aptr++;     // Swap the pointers. 
    fptr--; 
} 

afvalue = 0x0000; 

Теперь aptr точки к высоким битам и fptr указывает на младшие разряды, независимо от того, работает ли эмулятор на маленькой конечной или большой endian машине.

Примечание: поскольку эти типы указателей различны, компилятор может не знать, что они указывают на ту же область памяти, тем самым оптимизируя код таким образом, что запись в *afptr не видел позже чтения из *aptr. Поэтому volatile.

+2

Летучие не имеют ничего общего со строгим псевдонимом. Если типы несовместимы, это неопределенное поведение. – 2501

Смежные вопросы