2016-07-21 86 views
4

Ich bin versucht, einen Gameboy-Emulator in C, und bin derzeit in dem Prozess zu schreiben, zu entscheiden, wie das folgende Verhalten zu implementieren:Ist es möglich, zwei uint8_t-Zeiger auf die erste und zweite Hälfte eines Wertes zu erstellen, auf den ein uint16_t-Zeiger zeigt?

  • Zwei 8-Bit-Register kombiniert und als ein einzigen behandelt werden können 16- Bitregister
  • den Wert eines der 8-Bit-Register in der Pairing-Wechsel sollte

beispielsweise den Wert des kombinierten Registers ändern, Register A und F, die Register 8-Bit sind, können sein gemeinsam als das 16-Bit-Register AF verwendet. Wenn sich jedoch der Inhalt der Register A und F ändert, sollten sich diese Änderungen in nachfolgenden Verweisen zum Registrieren von AF widerspiegeln.

Wenn ich Register AF als uint16_t* implementiere, kann ich den Inhalt der Register A und F als uint8_t* 's speichern, der auf das erste bzw. zweite Byte des Registers AF zeigt? Wenn nicht, würden andere Vorschläge geschätzt werden :)

EDIT: Nur um zu klären, ist dies eine sehr ähnliche Architektur wie die Z80

+2

Versuchen Sie eine Union ... aber achten Sie auf Endianness. – Dmitri

+0

es ist auch ähnlich zu x86 –

Antwort

3

Verwenden Sie eine Vereinigung.

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

Die Mitglieder a und b teilen sich die Bytes. Wenn ein Wert in Element a geschrieben und dann mit Element b gelesen wird, wird der Wert in diesem Typ neu interpretiert. Dies könnte eine Trap-Repräsentation sein, die zu undefiniertem Verhalten führen würde, aber die Typen uint8_t und uint16_t haben sie nicht.

Ein weiteres Problem ist endianness, in das erste Elemente des Mitglieds Schreiben a wird immer das erste Byte des Elements ändern b, aber je nach endianness dass Byte könnte die meisten oder am wenigsten signifikante Bits b darstellen, so dass der resultierende Wert unterscheidet sie über Architekturen.


Zum Abfangen Darstellungen und endianness, sondern nur verwenden, um die Art uint16_t und schreibt in sie mit bitweise Operationen zu vermeiden. Zum Beispiel in die höchstwertigen 8 Bits schreiben:

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

und in ähnlicher Weise für die am wenigsten signifikanten 8 Bits:

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

Diese Operationen in eine Funktion gelegt werden sollte.

Die Umwandlungen zu (unsigned int) sind da, um Integer-Promotions zu vermeiden. Wenn INT_MAX gleich 2^15-1 ist und es Fall-Repräsentationen für vorzeichenbehaftete Ganzzahlen gibt, könnte der Vorgang: technisch undefiniertes Verhalten verursachen.

+0

Bedeutet das, ich müsste verwenden: AF.A, AF.F, AF.AF wann immer ich auf Register einzeln zugreifen muss? Ändert das Ändern eines Teils das Ganze? –

+0

@JoshThieme Es tut, aber sei vorsichtig. Ich würde vorschlagen, dass Sie die zweite Option verwenden. Siehe das Update. – 2501

+0

Vielen Dank für die eingehende Antwort. Ich glaube jedoch, dass aufgrund der Funktionsweise einiger CPU-Anweisungen a und b Zeiger auf ganze Zahlen statt ganze Zahlen sein müssen. Kann dies an diesen Fall angepasst werden? –

-1

Sie können natürlich Zeiger verwenden, um überall hin zu zeigen, wo Sie möchten.

#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); 
} 

Sie müßten Pflege von Endian nehmen, wie im Kommentar erwähnt ( Ich glaube, die gameboy Little-Endian, während die x86-Big-Endian ist).

Und natürlich wie die meisten poeple empfehlen sollten Sie einen union verwenden, die Ihr Code weniger fehleranfällig aufgrund Zeigeradresse Berechnungen machen würde

0

Man könnte es so lösen:

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; 

Jetzt aptr Punkte zu den hohen Bits und fptr zeigt auf die niedrigen Bits, egal ob der Emulator auf einem kleinen Endian oder Big-Endian-Maschine läuft.

Hinweis: Da diese Zeigertypen unterschiedlich sind, weiß ein Compiler möglicherweise nicht, dass er auf denselben Speicher verweist, wodurch der Code so optimiert wird, dass ein Schreiben auf *afptr von einem späteren Lesen aus *aptr nicht erkannt wird. Daher die volatile.

+2

Volatile hat nichts mit strengem Aliasing zu tun. Wenn die Typen nicht kompatibel sind, ist das Verhalten nicht definiert. – 2501