2016-07-21 14 views
4

내가 C에서 게임 보이 에뮬레이터를 쓰기를 시도하고 다음과 같은 동작을 구현하는 방법을 결정하는 과정에서 현재 생각 해요 :uint16_t 포인터가 가리키는 값의 첫 번째와 두 번째 절반에 두 개의 uint8_t 포인터를 만들 수 있습니까?

  • 2 개의 8 비트 레지스터가 결합 된 단일로 취급 할 수있다 16 비트, 8 비트 레지스터이다 A와 F가 등록 결합 레지스터 예

의 값을 변경한다 페어링의 8 비트 레지스터 중 하나의 값을 변경

  • 를 등록 할 수있다 16 비트 레지스터 AF로 공동으로 사용됩니다. 그러나 레지스터 A와 F의 내용이 변경되면 이러한 변경 사항이 후속 참조에 반영되어 AF를 등록해야합니다.

    레지스터 AF를 uint16_t*으로 구현하면 레지스터 AF의 첫 번째와 두 번째 바이트를 각각 가리키는 uint8_t*으로 레지스터 A 및 F의 내용을 저장할 수 있습니까? 그렇지 않다면, 어떤 다른 제안은 감사하겠습니다 :)

    편집 : 그냥 명확하게, 이것은 Z80

  • +2

    노동 조합을 시도하십시오 ... 그러나 엔디안에주의하십시오. – Dmitri

    +0

    또한 x86과 비슷합니다 –

    답변

    3

    사용에 매우 유사한 아키텍처 노동 조합이다.

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

    구성원 a와 b는 바이트를 공유합니다. 값이 구성원 a에 쓰여지고 구성원 b을 사용하여 읽으면 값은이 유형으로 다시 해석됩니다. 이것은 정의되지 않은 동작을 일으키는 트랩 표시 일 수 있지만 uint8_t 및 uint16_t 유형에는 정의되지 않은 동작이 발생합니다.

    또 다른 문제는 항상 부재 b의 첫번째 바이트가 변경됩니다 부재 a의 첫 번째 요소로 작성하지만 바이트 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); 
    } 
    

    당신은 ( 는 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 포인트 에뮬레이터가 리틀 엔디안 또는 빅 엔디 언 (big endian) 시스템에서 실행되는지 여부에 관계없이 높은 비트로, fptr은 낮은 비트를 가리 킵니다.

    참고 : 이러한 포인터 유형이 다르므로 컴파일러는 동일한 메모리를 가리킬 수 있으므로 *afptr에 대한 쓰기가 *aptr에서 나중에 읽지 않도록 코드를 최적화 할 수 있습니다. 따라서 volatile.

    +2

    휘발성은 엄격한 앨리어싱과 관련이 없습니다. 형식이 호환되지 않으면 정의되지 않은 동작입니다. – 2501