2010-12-08 2 views
3

이것은 교수가 자신의 스크립트에서 우리에게 보여준 것입니다. 필자가 작성한 코드에서이 메서드를 사용하지 않았습니다. 기본적으로구조체 또는 클래스를 파일로 저장하기 위해 재 해석 캐스팅 사용

, 우리는 클래스 또는 구조체를 가지고, 그것을 reinterpret_cast 그래서 같은 전체 구조체 오프 저장 : 이것은 (파일에) 출력을 생성

struct Account 
{ 
    Account() 
    { } 
    Account(std::string one, std::string two) 
     : login_(one), pass_(two) 
    { } 

private: 
    std::string login_; 
    std::string pass_; 
}; 

int main() 
{ 
    Account *acc = new Account("Christian", "abc123"); 

    std::ofstream out("File.txt", std::ios::binary); 
    out.write(reinterpret_cast<char*>(acc), sizeof(Account)); 
    out.close(); 

ÍÍÍÍChristian ÍÍÍÍÍÍ    ÍÍÍÍabc123 ÍÍÍÍÍÍÍÍÍ  

I을 혼란스러워. 이 방법은 실제로 작동합니까, 아니면 UB가 발생합니까? 개별 컴파일러의 변덕에 속하는 클래스와 구조체 내에서 불가사의 한 일이 발생하기 때문입니다.

+2

Yikes. 이것은, 예를 들어'std :: string'의 내부 표현에 의존하는 것처럼 보입니다. 게다가'struct' 패딩과 컴파일러가 할 수있는 다른 것들을 고려하지 않습니다. – Nate

+2

사실,'std :: string'의 구현이 동적으로 버퍼를 할당하면, 이것은 작동하지 않습니다. 표준 라이브러리가'std :: string' 객체 자체에 짧은 문자열을 캐시하는 것처럼 보이지만, 긴 문자열에는 실패 할 것입니다. – Nate

+0

이 방법을 사용하여 "무언가"를 저장할 수 있지만 같은 방식으로 다시 읽을 때 POD가 아닌 필드가 생존 할 것으로 기대하지 마십시오. – Blastfurnace

답변

8

실제로 작동하지 않지만 정의되지 않은 행동.

C++에서는 임의의 객체를 char의 배열로 재 해석하므로 여기에는 정의되지 않은 동작이 없습니다.

그러나 결과는 클래스가 POD (실제로 클래스가 단순한 C 스타일의 구조체 인 경우) 및 자체 포함 된 경우에만 사용할 수 있습니다 (즉, 구조체에 포인터 데이터 멤버가 없음) .

여기에 std::string 명의 회원이 있기 때문에 Account은 POD가 아닙니다. std::string의 내부는 구현에 따라 정의되지만 POD는 아니며 일반적으로 실제 문자열이 저장된 일부 힙 할당 블록을 참조하는 포인터가 있습니다 (특정 예에서는 구현에 작은 문자열 최적화가 사용됩니다. 문자열의 값은 std::string 개체 자체에 저장됩니다.

  • 당신은 항상 당신이 기대하는 결과를 얻을하지 않을 수 있습니다 :

    은 몇 가지 문제가 있습니다. 문자열이 더 길다면 std::string은 힙에 할당 된 버퍼를 사용하여 문자열을 저장하므로 포인터를 직렬화하는 것으로 끝나게됩니다.

  • 여기에 일련 번호를 기입 한 데이터는 실제로 사용할 수 없습니다. 데이터를 Account으로 다시 해석하고 작동 할 것으로 기대할 수는 없습니다. 왜냐하면 std::string 생성자가 호출되지 않기 때문입니다.

간단히 말해 복잡한 데이터 구조를 직렬화하는 데이 방법을 사용할 수 없습니다.

+0

"정의되지 않은 동작을 발생시키지 않습니다."- 마지막에 암시 하듯이 비 직렬화를 시도 할 때도 마찬가지입니다. –

+0

"클래스가 POD 인 경우 사용할 수 있습니다."다시로드시 올바른 정렬을 보장하는 중요성이 누락됩니다. –

+0

@Tony :로드하는 올바른 char 배열은로드 된'char' 배열의 실제 객체로 바이트를 복사하는 것입니다. 타입'Account'; 올바른 정렬이 보장됩니다. –

4

이것은 구조체의 내용과 데이터를 다시 읽는 플랫폼에 따라 작동 할 수 있습니다. 이는 교사가 전파해서는 안되는 위험한 휴대용이 아닌 해킹입니다.

구조체에 포인터 또는 int이 있습니까? 포인터는 다시 읽을 때 새 프로세스에서 유효하지 않으며 모든 컴퓨터에서 int 형식이 동일하지 않습니다 (이 방법에서는 두 가지 표시 중지 문제가 있음). 객체 그래프의 일부로 지적 된 것은 처리되지 않습니다. 구조체 패킹은 대상 머신 (32 비트 대 64 비트) 또는 동일한 하드웨어에서 변경되는 컴파일러 옵션으로 인해 다를 수 있으므로 sizeof(Account)을 리드 백 데이터 크기로 신뢰할 수 없게 만듭니다.

더 나은 솔루션을 찾으려면직렬화 라이브러리를 참조하십시오. Boost.Serialization이 좋은 예입니다. 여기

, 우리는 바이트 시퀀스에 C++ 데이터 구조의 임의의 세트의 가역 해체를 의미하는 용어 "시리얼" 를 사용한다. 이와 같은 시스템을 사용하면 다른 프로그램 컨텍스트에서 과 동일한 구조를 다시 구성 할 수 있습니다 ( ). 컨텍스트에 에 따라 개체 지속성 구현, 원격 매개 변수 전달 또는 기타 기능을 사용할 수 있습니다.

Google Protocol Buffers도 간단한 개체 계층 구조에 적합합니다.

+0

+1 포인터에 대한 언급 , 그리고 serialization에 대한 훌륭한 정의 – Thanatos

+0

@Thanatos - 정의는 Boost docs fyi의 인용문입니다. 교수님이 말한 것처럼 들리지는 않습니다. –

+0

'std :: string'의 구현이라고 주장하는 구조체는 임의의 (잘, 거의) 길이의 문자열을 처리 할 수 ​​있어야하고 따라서 동적 할당과 포인터를 사용할 수 있어야하기 때문에 작동합니다. 예제 struct는 std :: string 인스턴스를 포함하고 있으므로 결과적으로 작동하지 않습니다. 원칙이 재귀 적으로 적용됩니다. 비록 내가 "탈구축"이라기보다 "변형"을 말할지라도 그 정의는 훌륭하다고 동의했다. –

4

정의되지 않았습니다. 오히려 플랫폼 의존적이거나 구현 정의 된 동작입니다. 이것은 일반적으로 잘못된 코드입니다. 동일한 컴파일러의 다른 버전 또는 동일한 컴파일러의 다른 스위치가 저장 파일 형식을 손상시킬 수 있기 때문입니다.

1

올바른 직렬화를 대체 할 수 없습니다. 포인터를 포함하는 복잡한 유형의 경우를 생각해보십시오. 포인터를 파일에 저장하면 나중에로드 할 때 포인터가 의미있는 것을 가리 키지 않습니다.

또한 코드가 변경되거나 다른 컴파일러 옵션으로 다시 컴파일되는 경우에도 문제가 발생할 수 있습니다.

그래서 단순한 유형의 단기 저장에 유용합니다. 그렇게하면 작업에 필요한 것보다 더 많은 공간을 차지합니다.

1

이 방법은 전혀 작동하지 않을 경우 강력하지 않습니다. 바이너리, 텍스트, XML 등 무엇이든 "직렬화 된"형식을 결정하고이를 작성하는 것이 훨씬 낫습니다.

여기의 키 : 클래스 또는 구조체를 일련의 바이트로 /에서 안정적으로 변환하려면 함수/코드가 필요합니다. reinterpret_cast 클래스 또는 구조체를 나타내는 데 사용되는 메모리의 정확한 바이트가 패딩, 멤버 순서 등과 같이 바뀔 수 있으므로 은이 작업을 수행하지 않습니다.

1

번호

순서가 작동하려면, 구조는 POD (일반 오래된 데이터이어야에서 : 단순한 데이터 멤버 및 POD 데이터 멤버, 아니 가상 함수 ... 내가 할 수있는 아마 다른 제한 기억이 안나요). 표준 : : 당신이 일반 배열을 필요 했어, 그래서 문자열은 POD 아니다

struct Account { 
    char login[20]; 
    char password[20]; 
}; 

참고 : 당신이 그렇게하기를 원한다면

그래서, 당신은이 같은 구조체가 필요합니다 것입니다.

여전히 좋은 접근 방법이 아닙니다. 키워드 : "serialization".

+1

구조체가 널 종결 자로 끝나야 할 필요는 없습니다. 결과에 C 문자열 함수를 호출하기로 결정한 경우에만 필요합니다.이 형식은 올바르지 않습니다 ('int'에는 0 바이트가있을 것이므로 해당 문자열을 사용하십시오. ...) – Thanatos

+0

쓸 바이트 수를 지정할 수 있으면 널 종결자가 필요하지 않습니다. – Blastfurnace

+0

@ Thanatos, 예, 게시물을 게시하고 편집 한 후 몇 초를 기록했습니다. – Kos

0

문자열의 일부 버전은 실제로 문자열이 작은 경우 문자열에 동적 메모리를 사용합니다. 따라서 문자열 객체에 문자열을 내부적으로 저장하십시오. 이것의

생각해 : 64 비트 시스템에서 이제

struct SimpleString 
{ 
    char* begin;  // beginning of string 
    char* end;   // end of string 
    char* allocEnd;  // end of allocated buffer end <= allocEnd 
    int*  shareCount; // String are usually copy on write 
          // as a result you need to track the number of people 
          // using this buffer 
}; 

. 각 포인터는 8 바이트입니다. 따라서 32 바이트 미만의 문자열은 버퍼를 할당하지 않고 동일한 구조에 적합 할 수 있습니다.

struct CompressedString 
{ 
    char buffer[sizeof(SimpleString)]; 
}; 
stuct OptString 
{ 
    int  type;  // Normal /Compressed 
    union 
    { 
     SimpleString  simple; 
     CompressedString compressed; 
    } 
}; 

그래서 나는 이것이 위에서 일어난다 고 생각합니다.
매우 효율적인 문자열 구현이 사용되어 포인터에 대한 걱정없이 파일에 객체를 덤프 할 수 있습니다 (std :: string은 포인터를 사용하지 않으므로).

분명히 std :: string의 구현 세부 사항에 따라 이식성이 떨어집니다.

재미있는 트릭이지만 이식 가능하지 않습니다. (일부 컴파일 시간 검사없이 쉽게 깨지기 쉽습니다.)

+0

... 심지어 문자열이 "짧을"것을 보장 할 수 없다면 쓸모가 없습니다. –

관련 문제