문제

오픈 소스에서 내가 쓴 프로그램, 파일에서 바이너리 데이터 (다른 프로그램에 의해 작성)를 읽고 INT, 복식 및 기타 다양한 데이터 유형을 출력합니다. 과제 중 하나는 두 엔디 니스의 32 비트 및 64 비트 머신에서 실행해야한다는 것입니다. 즉, 약간의 낮은 수준의 비트 틀링을해야한다는 것을 의미합니다. 나는 유형의 punning과 엄격한 별칭에 대해 (매우) 조금 알고 있으며, 내가 올바른 방식으로 일을하고 있는지 확인하고 싶습니다.

기본적으로 숯*에서 다양한 크기의 int로 쉽게 변환 할 수 있습니다.

int64_t snativeint64_t(const char *buf) 
{
    /* Interpret the first 8 bytes of buf as a 64-bit int */
    return *(int64_t *) buf;
}

그리고 필요에 따라 바이트 주문을 바꾸는 지원 기능이 있습니다.

int64_t swappedint64_t(const int64_t wrongend)
{
    /* Change the endianness of a 64-bit integer */
    return (((wrongend & 0xff00000000000000LL) >> 56) |
            ((wrongend & 0x00ff000000000000LL) >> 40) |
            ((wrongend & 0x0000ff0000000000LL) >> 24) |
            ((wrongend & 0x000000ff00000000LL) >> 8)  |
            ((wrongend & 0x00000000ff000000LL) << 8)  |
            ((wrongend & 0x0000000000ff0000LL) << 24) |
            ((wrongend & 0x000000000000ff00LL) << 40) |
            ((wrongend & 0x00000000000000ffLL) << 56));
}

런타임에 프로그램은 기계의 엔지니어를 감지하고 위의 중 하나를 함수 포인터에 할당합니다.

int64_t (*slittleint64_t)(const char *);
if(littleendian) {
    slittleint64_t = snativeint64_t;
} else {
    slittleint64_t = sswappedint64_t;
}

이제 까다로운 부분은 숯*을 두 배로 캐스팅하려고 할 때 발생합니다. Endian-Swapping 코드를 그렇게 재사용하고 싶습니다.

union 
{
    double  d;
    int64_t i;
} int64todouble;

int64todouble.i = slittleint64_t(bufoffset);
printf("%lf", int64todouble.d);

그러나 일부 컴파일러는 "int64todouble.i"할당을 최적화하고 프로그램을 중단 할 수 있습니다. 이 프로그램이 성능을 위해 최적화되어야한다는 점을 고려하면서도 더 안전한 방법이 있습니까? 또한 Char*를 직접 두 배로 캐스트하기 위해 병렬 변환 세트를 작성하지 않기를 원합니까? Punning의 노조 방법이 안전하다면 SnativeInt64_t와 같은 기능을 다시 작성해야합니까?


나는 결국 사용했다 Steve Jessop 's 답변 변환 기능이 Memcpy를 사용하도록 다시 작성했기 때문에 다음과 같이 답하십시오.

int64_t snativeint64_t(const char *buf) 
{
    /* Interpret the first 8 bytes of buf as a 64-bit int */
    int64_t output;
    memcpy(&output, buf, 8);
    return output;
}

내 원래 코드와 동일한 어셈블러로 컴파일되었습니다.

snativeint64_t:
        movq    (%rdi), %rax
        ret

둘 중에서, Memcpy 버전은 내가하려는 일을 더 명시 적으로 표현하고 가장 순진한 컴파일러조차도 작동해야합니다.

아담, 당신의 대답도 훌륭했고 나는 그것으로부터 많은 것을 배웠습니다. 게시 해 주셔서 감사합니다!

도움이 되었습니까?

해결책

int64_t와 double이 크기가 같고 적절한 스토리지 표현이 있는지 확인하기 위해 구현에 대해 충분히 알고있는 것 같습니다. 그런 다음 별명에 대해 생각조차 할 필요조차 없습니다.

여러 바이너리를 출시하려는 경우 쉽게 감소 할 수있는 함수에 대한 기능 포인터를 사용하기 때문에 성능은 큰 문제가되어서는 안되지만 일부 컴파일러가 Memcpy를 최적화하는 것을 상당히 최적화 할 수 있음을 알고 싶을 수도 있습니다. 소형 정수 크기의 경우 부하 및 매장 세트를 인쇄 할 수 있으며 변수가 완전히 최적화되어 있으며 컴파일러는 "사본"을 조합처럼 변수에 사용하는 스택 슬롯을 단순히 재 할당합니다.

int64_t i = slittleint64_t(buffoffset);
double d;
memcpy(&d,&i,8); /* might emit no code if you're lucky */
printf("%lf", d);

결과 코드를 검사하거나 프로파일을 프로파일 링하십시오. 최악의 경우에도 느리지 않을 것입니다.

그러나 일반적으로, 바이트 스왑으로 너무 영리한 일을하면 휴대 성 문제가 발생합니다. 각 단어는 작은 엔디언 인 중간 렌디안 복식이있는 ABI가 있지만 큰 단어가 먼저옵니다.

일반적으로 Sprintf 및 SSCANF를 사용하여 복식을 저장하는 것을 고려할 수 있지만 프로젝트의 경우 파일 형식이 제어되지 않습니다. 그러나 애플리케이션이 삽질되는 경우 IEEE가 입력 파일에서 한 형식의 입력 파일에서 다른 형식의 출력 파일로 두 배로 늘어선 경우 (문제의 데이터베이스 형식을 모르기 때문에 확실하지 않은지 확실하지 않음) 어쨌든 산술에 사용하지 않기 때문에 두 배라는 사실을 잊을 수 있습니다. 파일 형식이 다른 경우에만 바이트 스왑이 필요한 불투명 숯으로 취급하십시오 [8].

다른 팁

나는 당신이 읽는 것이 좋습니다 엄격한 별칭 이해. 구체적으로, "노동 조합을 통한 캐스팅"이라는 섹션을 참조하십시오. 그것은 매우 좋은 예가 많이 있습니다. 이 기사는 셀 프로세서에 관한 웹 사이트에 있으며 PPC 어셈블리 예제를 사용하지만 거의 모든 것이 X86을 포함한 다른 아키텍처에도 동일하게 적용됩니다.

이 표준은 노조의 한 분야에 글을 쓰고 그것을 읽는 것은 즉시 정의되지 않은 행동이라고 말합니다. 따라서 규칙 책을 사용하면 노조 기반 방법이 작동하지 않습니다.

매크로는 일반적으로 나쁜 생각이지만 이것은 규칙에 대한 예외 일 수 있습니다. 입력 및 출력 유형을 매개 변수로 사용하여 일련의 매크로 세트를 사용하여 C에서 템플릿 형 동작을 얻을 수 있어야합니다.

매우 작은 하위 목표로 64 비트 케이스에서 마스킹과 변속을 교환 할 수 있는지 조사하는 것이 좋습니다. 작업이 바이트를 바꾸는 것이므로 항상 마스크 마스크로 도망 갈 수 있어야합니다. 0xff. 컴파일러가 그 자체로 알아낼 수있을 정도로 똑똑하지 않으면 더 빠르고 컴팩트 한 코드로 이어질 것입니다.

간단히 말해서 이것을 바꾸십시오 :

(((wrongend & 0xff00000000000000LL) >> 56)

이것으로 :

((wrongend >> 56) & 0xff)

동일한 결과를 생성해야합니다.

편집하다:
질문자가 다른 프로그램이 자신의 데이터를 작성한다고 언급하지 않았기 때문에 항상 데이터를 효과적으로 저장하고 Machine Endianess로 바꾸는 방법에 대한 의견을 제거했습니다.

그래도 데이터가 모든 엔디언에서 큰 및 호스트 엔디언으로 전환 해야하는 경우, NTOHS/NTOHL/HTONS/HTONL은 최상의 방법이며, 가장 우아하고 무적으로 속도가 높습니다 (CPU가 지원하는 경우 하드웨어에서 작업을 수행 할 수 있으므로 그것을 이길 수 없습니다).


더블/플로트와 관련하여 메모리 캐스팅으로 int에 저장하십시오.

double d = 3.1234;
printf("Double %f\n", d);
int64_t i = *(int64_t *)&d;
// Now i contains the double value as int
double d2 = *(double *)&i;
printf("Double2 %f\n", d2);

함수로 싸서

int64_t doubleToInt64(double d)
{
    return *(int64_t *)&d;
}

double int64ToDouble(int64_t i)
{
    return *(double *)&i;
}

질문자는 다음 링크를 제공했습니다.

http://cocoawithlove.com/2008/04/using-pointers-to-recast-in-c-is-bad.html

캐스팅이 나쁘다는 것을 증명합니다 ... 불행히도 나는이 페이지의 대부분에 대해서만 강력하게 동의하지 않을 수 있습니다. 인용문 및 의견 :

포인터를 통해 주조하는 것만 큼 일반적으로, 실제로는 나쁜 연습과 잠재적으로 위험한 코드입니다. 포인터를 통해 캐스팅하면 유형의 펀딩으로 인해 버그를 만들 수 있습니다.

그것은 전혀 위험하지 않으며 나쁜 연습도 아닙니다. C의 프로그래밍과 마찬가지로 버그를 잘못 수행하면 버그를 일으킬 수있는 잠재력 만 있습니다. 버그가 잘못 수행되면 버그를 일으킬 가능성이 있으므로 모든 언어의 프로그래밍도 마찬가지입니다. 그 주장에 의해 당신은 프로그래밍을 완전히 중지해야합니다.

Punning을 입력하십시오
두 개의 포인터가 메모리의 동일한 위치를 참조하고 해당 위치를 다른 유형으로 나타내는 포인터 별칭 형태. 컴파일러는 두 "pun"을 관련없는 포인터로 취급합니다. 유형 Punning은 두 포인터를 통해 액세스 할 수있는 데이터에 대한 종속성 문제를 일으킬 가능성이 있습니다.

이것은 사실이지만 불행히도 내 코드와 완전히 관련이 없습니다.

그가 말하는 것은 다음과 같은 코드입니다.

int64_t * intPointer;
:
// Init intPointer somehow
:
double * doublePointer = (double *)intPointer;

이제 더블 포인터와 intpointer는 동일한 메모리 위치를 가리 키지 만 동일한 유형으로 취급합니다. 이것은 당신이 실제로 노동 조합과 해결해야 할 상황입니다. 다른 것은 꽤 나쁜 것입니다. 내 코드가하는 일이 아닙니다!

내 코드가 , 참조. 나는 INT64 포인터 (또는 다른 방법)를 두 배로 던져 버렸다. 즉시 연기 그것. 함수가 돌아 오면 포인터가 아무것도 보관되지 않습니다. int64와 double이 있으며 이들은 함수의 입력 매개 변수와 완전히 관련이 없습니다. 나는 다른 유형의 포인터에 대한 포인터를 복사하지 않습니다 (코드 샘플에서 이것을 보았을 때 내가 쓴 C 코드를 강력하게 잘못 읽습니다), 나는 단지 다른 유형 (자체 메모리 위치)의 변수로 값을 전송합니다. . 따라서 Punning의 정의는 "메모리의 동일한 위치를 참조하십시오"라고 말하면서 전혀 적용되지 않으며 여기서는 동일한 메모리 위치를 나타내는 것은 없습니다.

int64_t intValue = 12345;
double doubleValue = int64ToDouble(intValue);
// The statement below will not change the value of doubleValue!
// Both are not pointing to the same memory location, both have their
// own storage space on stack and are totally unreleated.
intValue = 5678;

내 코드는 외부 기능이없는 C로 작성된 메모리 사본에 지나지 않습니다.

int64_t doubleToInt64(double d)
{
    return *(int64_t *)&d;
}

작성할 수 있습니다

int64_t doubleToInt64(double d)
{
    int64_t result;
    memcpy(&result, &d, sizeof(d));
    return result;
}

그 이상은 아무것도 아니기 때문에 어디서나 시야에도 유형이 없습니다. 그리고이 작업은 작동이 C에서 안전 할 수 있으므로 안전합니다. 더블은 항상 64 비트로 정의됩니다 (크기가 다양하지 않으면 64 비트로 고정되어 있음) 따라서 항상 적합합니다. int64_t 크기 변수로.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top