일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Adapter 패턴
- 하이퍼레저 패브릭
- hyperledger fabric
- 하이브리드앱
- Play2
- 블록체인
- Akka
- 파이썬 강좌
- 파이썬 데이터분석
- 엔터프라이즈 블록체인
- play2 강좌
- 이더리움
- Play2 로 웹 개발
- 그라파나
- 주키퍼
- 파이썬 머신러닝
- Hyperledger fabric gossip protocol
- Golang
- CORDA
- 플레이프레임워크
- 파이썬
- akka 강좌
- 파이썬 동시성
- Actor
- 스칼라
- play 강좌
- 안드로이드 웹뷰
- 스칼라 동시성
- 스칼라 강좌
- 스위프트
- Today
- Total
HAMA 블로그
C API 와 C++(STL) 호환에 관한 팁 본문
C++만 사용하고 싶어도 태생적 한계 때문에 C API 와의 호환에 대하여 항상 염두해 둬야 하는게 C++ 개발자들의 숙명입니다. 바이트배열로 직렬화 하는 부분에서도 이러한 랑데뷰가 발생하는데 이번 포스트에서 이에 대한 내용을 정리 하려고 합니다. 구체적으로는 int 형을 char* 로 바꾸는 방식에 대해서 살펴 봅니다.
* 직접 메모리를 할당하는 경우에는 메모리 해제하는 부분등에서 오류를 범할 확률이 높아질테고, std::vector , std::string 등을 사용하면 그런 부분은 해결되나 라이브러리에 대한 이해 없이 사용 할 경우 미묘한 버그를 만들어 내서 더욱 찾기가 어렵게 만들 수 도 있습니다. 모든게 좋은 건 세상에 없지요~ 트레이드오프!!
C memcpy
unsigned char * arr= (unsigned char*)malloc(8);
uint64_t x = 200321;
memcpy(arr, &x, 8);
- memcpy를 이용하면 간단히(형변환없이) uint64 타입의 숫자를 바이트배열로 직렬화 할 수 있습니다.
- memcpy는 무엇이 매개변수로 들어 오던 void* 로 받아서 바이트배열로 처리 하니까요.
- 동적으로 할당한 메모리를 해제시켜주는 것을 잊지 마세요.
- unsigned char arr[8]; 이렇게 스택에 만들어주는게 낫겠죠.
unsigned char * arr = (unsigned char*)malloc(100);
uint64_t x = 200321;
memcpy(arr, &x, 8);
uint32_t y = 52;
memcpy(arr + 8 , &y, 4);
- 이렇게 배열을 크게 잡아서 다양한 타입의 직렬화를 구성할 수도 있을 것입니다.
C++ std::vector
uint64_t x = 200321;
std::vector<uint8_t> arr(8);
std::copy((uint8_t*)&x, (uint8_t*)&x + 8, &arr[0]);
- vector의 시작 포인터는 &arr[0]; 과 같이 추출할 수 있습니다.
- std::copy 를 이용합니다. 성능은 memcpy와 비슷하며 타입에 대해 좀 더 따지게 됩니다.
- 만약 std::copy(&x, &x + 8, &arr[0]); 이렇게 한다면?? 재앙이 일어 날 겁니다. 오류가 생기진 않죠. 따라서 해결하기 어렵게 될 수 있습니다. 여기서 &x + 8 은 8바이트 뒤의 주소가 아니라, 64바이트가 됩니다.
- vector 는 자동으로 메모리 관리를 해주므로 더 많은 메모리가 필요하면 자동으로 추가 됩니다.
- vector 의 재할당 때문에 느려지는 속도가 염려스러우면 reserve 로 미리 많이 잡아 두면 됩니다.
std::vector<uint8_t> vec(100);
uint64_t _blockNum1 = 543210;
std::copy((uint8_t*)&_blockNum1, (uint8_t*)&_blockNum1 + 8, &vec[0]);
uint32_t _blockNum2 = 12;
std::copy((uint8_t*)&_blockNum2, (uint8_t*)&_blockNum2 + 4, &vec[8]);
- 이렇게 배열을 미리 크게 잡아두고 다양한 타입을 직렬화 할 수 도 있습니다.
다시 숫자타입으로 돌리려면 아래와 같이 하면 될 거 같습니다.
방법1)
uint64_t x = *(uint64_t*)(&vec[0]);
std::cout << "result : " << std::setprecision(10) << x << std::endl;
방법2)
void toUint64(uint8_t * c , size_t n) {
uint64_t i = (c[7] << 56) | (c[6] << 48) | (c[5] << 40) | (c[4]) << 32 | (c[3]) << 24 | (c[2]) << 16 | (c[1]) << 8 | (c[0]);
std::cout << "result : " << i << std::endl;
}
C++ std::string
int x = 200321;
std::string arr;
arr.append((const char*)&x, sizeof(int));
- std::string 또한 훌륭히 바이트배열을 조작하는데 사용 할 수 있습니다.
- append 에는 바이트포인트와 타입의 길이가 들어 갑니다.
이것은 아래와 같이 다시 int로 되돌릴 수 있을 것입니다.
const char* pointer = arr.c_str();
int y = *(int*)(pointer);
- c_str()은 vector에는 없고 string에만 있는 것으로 바이트 포인트를 리턴해 줍니다.
- &arr[0]; 해줘도 에러는 안나겠지만 지양해야합니다. vector와 다르게 string은 연속된 주소를 할당하지 않을 수 있습니다.
// 인코딩
std::setprecision(10);
double x = 2012621.12;
std::cout << "start : " << x << std::endl;
std::string arr;
arr.clear();
arr.append((const char*)&x, sizeof(double));
// 디코딩
const char* pointer = arr.c_str();
double y = *(double*)(pointer);
std::cout << "result : " << y << std::endl;
- 이렇게 어떤 타입이든 직렬화를 할 수 있습니다. string, wstring, map 등등
p.s
Effective STL 의 챕터 2는 아래와 같은 항목이 있습니다. 참고 하시구요.
Chapter 2 vector와 string
항목 13 : 동적으로 할당한 배열보다는 vector와 string이 낫다.
항목 14 : reserve는 필요 없이 메모리가 재할당되는 것을 막아 준다
항목 15 : 잊지 말자! string은 여러 가지 방식으로 구현되어 있다는 사실을...
항목 16 : 기존의 C API에 vector와 string을 넘기는 방법을 알아두자
항목 17 : 쓸데없이 남은 용량은 "바꿔치기(swap) 묘수"를 써서 없애 버리자
항목 18 : vector 보기를 돌같이 하자
'C++ (비공개)' 카테고리의 다른 글
C++ 50 계명 - 이것만은 기억하자. (1) | 2019.03.19 |
---|---|
스마트 포인터 종류 분석 - C++98, 11, TR1, boost - (0) | 2014.08.10 |