char* 에서 구조체 형변환 질문드립니다.

kysu5095의 이미지

typedef struct _my_struct{
int len,
char name[5],
}my_struct;

와 같이 구조체가 정의되어 있을 때 미리 정의되어 있는 char*를 구조체 배열로 변환하고 싶습니다.

// 구조체 5개를 담을 수 있는 char*
char* my_char = (char*)malloc(sizeof(my_struct) * 5);
// my_char를 my_struct배열로 형변환 하는 코드
???

검색해본 결과 char*이 struct와 사이즈가 같다면

char* my_char = (char*)malloc(sizeof(my_struct));
my_struct* tmp_struct = (my_struct*)my_char;

과 같이 구조체로 형변환하는 것까지 알았는데 char*을 구조체 '배열'로 형변환 하는 방법을 모르겠습니다.

조금 더 정확히는 char*이 my_struct배열로 형변환 되었을 때 my_struct[0]->len = 1, my_struct[4]->len = 3;
이런식으로 배열로 접근하고 싶습니다.

답변이나 힌트 남겨주시면 감사하겠습니다.

세벌의 이미지

kysu5095의 이미지

답변 감사합니다.
제가 사용하는 것은 Union이 아니라 구조체여서 어떤식으로 사용하시라는지 이해가 잘 안되는데 조금 더 설명 가능하신가요?

익명 사용자의 이미지

꼭 그렇게 하고 싶다면야, 동적 할당을 받은 메모리를 배열 타입으로 캐스팅할 수 있긴 합니다.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
typedef struct _my_struct{
    int len;
    char name[5];
} my_struct;
 
int main(){
    char *my_char = (char *)malloc(sizeof(my_struct) * 5);
    my_struct (*ptr_to_my_struct_array)[5] = (my_struct (*)[5])my_char;
 
    puts("input:");
    for(int i=0; i<5; ++i){
        printf("\t%d: ", i+1);
        scanf("%4s", &(*ptr_to_my_struct_array)[i].name);
        (*ptr_to_my_struct_array)[i].len = strlen((*ptr_to_my_struct_array)[i].name);
    }
 
    puts("output:");
    for(int i=0; i<5; ++i)
        printf("\t%d: %s(%d)\n", i+1, (*ptr_to_my_struct_array)[i].name, (*ptr_to_my_struct_array)[i].len);
 
    return 0;
}

근데 대체 왜 그렇게 하려는 건데요?

kysu5095의 이미지

답변 감사합니다.
기본으로 저장되어 있는 자료형은 char* 형식이고 중간중간 그 값을 가져와 구조체 형식으로 변환해서 사용한 다음
다시 char* 으로 저장하려고 해서 질문하였습니다. 너무 이상한가요..?ㅠ 저장하는 형식이 char*타입이라 다른 방도가 떠오르지 않았습니다ㅠㅠ

라스코니의 이미지

하시려는 바는 대충 알겠습니다.
그냥 type casting이면 되는 것 같아 보이는데,,,, 혹시 데이터가 char stream 식으로 들어오는데 그 중간 중간에 구조체 데이터(아마 배열?)가 들어오면 이것을 그 구조체로 casting 해서 쓰고자 하는 상황과 비슷한 건가요?

혹시 그렇다면 (아님 비슷하더라도) type casting해서 쓰시면 될 겁니다.

char *ptr; 에 데이터가 있다고 가정하고

(my_struct *)ptr[0]->len, (my_struct *)ptr[1]->len 이렇게 쓰시면 됩니다.

kysu5095의 이미지

헐 대단하시네요,,,
제가 봐도 개떡같이 질문한거 같은데 그걸 캐치하시네요,,,감사합니다ㅠㅠ
저도 언능 실력 상승시켜서 도움 청하시는분들 답변 달고 싶어집니다 감사합니다.

라스코니의 이미지

이런 캐스팅은 저도 많이 사용하기 때문입니다. 여러가지 형태의 단순형, 구조체형 데이터가 TCP/IP 등으로 스트림으로 들어오면 이렇게 사용할 수 밖에 없거든요.

한가지 조언을 추가로 드리면 각각 구조체의 포인터를 미리 만든 후 사용하시면 코드의 복잡도를 좀 더 줄일 수 있습니다. 왜냐하면 이미 뭘로 캐스팅해야 할지 알고 있는 상황이기 때문이죠.

struct AAA *ptrA;
struct BBB *ptrB;
 
...
 
if (type == AAA) {
   ptrA = (struct AAA *)stream;
   ptrA[0]->len = 0;
}

등등이죠.

Stephen Kyoungwon Kim@Google의 이미지

이건 alignment 문제 때문에 조심하셔야 됩니다. char stream은 1 바이트 alignment일 것 같은데, struct 타입은 N의 배수부터 시작 주소가 생긴다는 가정이 있습니다. 각 멤버마다 alignment rule이 있고 플랫폼마다 조금씩 다를 수도 있습니다. 컴파일러가 향후 그 struct가 사용되는 장면에서 그 가정 아래 코드를 생성하기 때문에 bus error 등이 발생할 수 있습니다. 또 보내는 쪽과 받는 쪽의 얼라인먼트 룰이 다르다면, 똑같은 내용의 struct를 덤프했을 때 메모리 레이아웃이 다른 문제도 생길 수 있겠구요.

라스코니의 이미지

alignment 부터 시작해서 엔디안, 패킹 이슈, 체크섬, 통신 중단 등 실제로 완벽한 솔류션이 나올때 까지는 산적한 문제가 많습니다. 그렇지만 하다보면 또 그렇게 복잡하지도 않죠. 왜냐하면 그 과정중에 대부분이 이미 잘 해결이 되어 있기 때문이죠. 그런 부분은 직접 하는 분의 즐거움으로 남겨놓는 걸로 하겠습니다.

Stephen Kyoungwon Kim@Google의 이미지

체크섬과 통신 중단은 다른 차원의 문제인 것 같고, 말씀하신 대로 엔디안, 패킹 등은 전부 바이너리 포맷으로 메모리 레이아웃을 그대로 덤프할 경우 그게 플랫폼마다 다르다는 문제 같습니다. 그밖에 같은 플랫폼 내에서도 내추럴 얼라인먼트 1인 어레이를 N인 스트럭트로 캐스팅하는 건 유지/보수라는 관점에서만 생각해 봐도 이상적인 솔루션처럼 보이지 않고요.

어떤 조건에서는 바이트를 덤프해서 보낼 수도 있다고 생각하지만 일반적인 practice로 보이지는 않습니다. 차라리 남들이 어떻게 데이터를 시리얼라이즈 해서 보내는지 찾아보는 게 더 낫다고 봅니다.

라스코니의 이미지

결론적으로 바이트를 그냥 덤프해서 동작한다고 가정하는 것은 맞지 않다는 것에 동의합니다. serializing 같은 접근이 원론적으로 필요한데, 관심있으신 분들은 CBOR 같은 기술을 참고하시기 바랍니다.

ymir의 이미지

이미 많은 구조를 가진 패킷들이 다양한 플랫폼을 통해 돌아다니고 있습니다.
거기에 사용자 패킷들이 더 추가된다고 해서 크게 문제될 이유는 없죠.
게다가 통신용 패킷들은 이미 어느 플랫폼이든 고정된 길이를 가진 data type 을 이용하여..
미리 packing 을 해두기 때문에, 이식성에서도 고려할 사항이 많지 않습니다.

byte array 와의 casting 은 주로..
데이터 복사 없이 곧바로 송/수신용 버퍼로 사용하는 과정에서 일어나는데..
다시 복사할 필요없이 포인터로 바로 접근할 수 있기 때문에..
상대적으로 시간 손실 없이 빠르게 데이터를 처리할 수 있습니다.

설령 통신용 구조체와 관리용 구조체가 달라서 일일이 복사해줘야 한다 하더라도..
어차피 serialize 하는 비용보다 쌉니다.

단, 이미 고정된 포맷을 갖고 있기 때문에, 프로토콜이 변경되어야 하는 경우에는..
이전 버전의 프로토콜과 호환시키기가 까다롭죠.
프로로토콜에 자주 변경이 일어난다면, 오히려 serialization 이 이득일 수도 있습니다.
또한, 가변 길이를 가진 데이터 때문에 패킷이 커지고, 트래픽이 많아진다면..
역시 serialization 을 고려해 볼 필요가 있구요.

또한, 다양한 언어로 구현된 서버/클라이언트와 통신을 해야 하는 경우에는..
google protocol buffer 와 같이 이미 구현되어 있는 language/platform neutral 한 솔루션이..
개발/유지보수에 훨씬 이득일 수도 있죠.

어쨌든 serialization 모듈을 도입하거나 구현하는 것도 일종의 비용이고..
시간/속도에 대한 영향도 고려해야 하기 때문에..
어느 쪽을 취하는게 이득일 지는, 자기 프로젝트 상황에 따라 좀 더 생각해 볼 여지가 있습니다.

되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』

Stephen Kyoungwon Kim@Google의 이미지

코멘트 감사합니다. 저는 네트웤 프로그래밍을 이번에 시작하기 전엔 거의 할 일이 없었으니 아마 ymir님께서 더 잘 알고 계시리라고 생각하는데요.

제 의도는 "어떤 조건에서는" 메모리를 덤프하는 것도 가능하다는 것이었고요. sequentize라는 말로 의미하고 싶었던 것은 플랫폼 중립적으로 사용 가능한, 널리 쓰이는 방식이었습니다. 제 머리 속에 있었던 건 이를테면, json library를 이용해서 json 같은 텍스트 포맷으로 바꾼 다음 보내서, 받는 쪽도 json 라이브러리를 통해 파싱해서 인-메모리에 로드하는 방식 같은 거였고요.

퍼포먼스의 영향 외에 개발/유지/보수 비용까지 고려해서 가장 맞는 쪽을 골라야 하고, 메모리를 덤프하는 것도 상황에 따라서는 고려될 수 있다는 데는 동의하구요.

"길이가 플랫폼 중립적으로 일정한 데이터 타입을 활용해야 하며" (int는 빠지는 거죠) pack을 해서 보낸다는 전제가 있다면 아마 이식성에서 고려할 부분은 엔디안 정도라고 생각합니다. (그 외에는 잘못된 char stream 상의 어느 위치를 집어서 캐스팅을 하게 되는 버그가 잠재적으로 가능하다는 정도 같습니다.)

다만 packed struct의 사용도 비용이 따를 수 있다는 정도를 추가하고 싶습니다. intel 같은 머신에선 그대로 써도 되겠지만 SPARC 같은 머신에서는 핫 룹에 넣을 수 있을 것 같지는 않습니다. packed는, 제가 이해하기론 misaligned memory access 로 보이고요. 그런 인스트럭션이 없거나 너무 레이턴시가 긴 SPARC 같은 아키텍쳐에서는 컴파일러가 복수의 얼라인드 메모리 억세스와 비트 오퍼레이션을 써야 할 거구요. 이런 플랫폼까지 고려한다면 packed로 받아 unpacked로 옮기거나, 혹은 퍼포먼스 로스를 감수하고 쓰거나 하겠죠.

만약 그냥 같은 플랫폼끼리, 심지어 한 머신 내에서 IPC처럼 쓰일 거고 앞으로도 그럴 거라면, 메모리 덤프도 충분히 간결하고 효율적이라고 생각합니다. 인텔 - 인텔끼리나, 어떤 속성을 갖는 아키텍쳐 집합 내의 원소들끼리 통신을 하는 거라면, 말씀대로 pack을 해서 보내고 받아서 캐스팅 해서 쓰는 게 간단하고 장점이 있는 솔루션일 테고요. 하지만 메모리 얼라인먼트가 다른 플랫폼에서 같은 구조체가 사용되는 상황이라면, 그렇게 간단한 문제는 아니겠죠. 그런 경우에는 플랫폼에 의존하지 않는 커뮤니케이션 방식을 쓰는 게 좋을 거라고 생각합니다.

하여튼 무턱대고 캐릭터 스트림을 받아서 캐스팅 해서 쓰겠다고 하는 게 "일반적으로" 옳은 건 아니죠.

라스코니의 이미지

저는 가급적 깊은 내용의 추가 의견을 길게 달지는 않을려고 합니다. 쓰레드가 길어지면 insulting으로 받아들이는 분들이 있어서요. 그래서 제 의견에 심각하게 동의하지 않는 분이 있어도 대응하지 않고 있습니다. 하지만 Stephen 님은 나름 합리적이고 기술적 논의를 추구한다고 생각하고 좀 적겠습니다.

대부분의 통신 및 상업적 소프트웨어에서 큰 데이터 전달 목적으로 TCP/IP와 같은 통신 채널을 통해 이기종 머신과 대화할 때 패킹 & 엔디안 이슈는 거의 발생하지 않습니다. 데이터는 무조건 byte 단위로 보내기 때문에 아시다시피 endian도 패킹 이슈도 없습니다. 오로지 엔디안 및 패킹 이슈는 패킷 헤더, 프레임 헤더 구조에서 발생할 수 있는데 이러한 문제는 통신 표준을 정하는 과정에 모두 사라집니다. 관련 분야에 경험이 많은 (서로 다른 회사나 기관의) 담당자들이 모여서 적게는 수개월 길게는 수십년간 개정해 오기 때문에 이런 간단한(?) 이슈는 금방 정리됩니다.

위의 상황은 좀 대규모의 통신 방식에 해당하는 문제이고, 아마 소형 내장형 통신 시스템의 경우 개발자가 단독으로 해결해야 하는 경우도 있을 수 있습니다. 이때 위의 경우처럼 serious communication architecture를 만드는 것은 '개발에 편자'와 같이 실정에 맞지 않기 때문에 대부분 ad hoc 통신 규격을 만들게 됩니다.

이런 환경에서는 모든 (상대방에 전달해야 하는) 정보를 specific frame format에 때려넣다 보니까 구조체가 길어지게 됩니다. 왜냐 이게 편하니까요. 구조체가 길어지면 그만큼 엔디안, 패킹 이슈가 혹시라도 있을 수 있습니다(사실 거의 문제가 되지 않습니다. 좀 하다보면 느낌을 알게 되기 때문에 조금만 이 분야에 경험을 쌓아도 크게 문제가 안되는 정도입니다). 이 frame format이 너무 길어지게 되면 json이나 xml, CBOR 등을 고민하게 되고 그보다 더 길어지게 되면 그때는 DB로 가게되는 겁니다.

ad hoc 정보 전달에서 문제가 될 수 있는 부분은 사용자가 한 머신에서 수백 바이트짜리 구조체를 만들고 이것 저것 하다가 그것으로 바로 send(, struct, sizeof(struct)) 해서 보낼려고 할 때죠. 이때 대부분 처음에 제대로 전달되지 않습니다. 그걸 디버깅하다 보면 엔디안, 패킹 문제가 있다는 것을 알게 되고 또 쉬운 문제이기 때문에 금방 해결됩니다.

Stephen Kyoungwon Kim@Google의 이미지

훨씬 더 이해가 잘 되었습니다. 어떤 상황에서 이런 식으로 기본적으로 struct 객체를 in-memory dump 해서 보내는지도 이해/공감이 더 되고요.

ymir의 이미지

말씀하신 대로, 강제로 packing 되어야 하는 애들의 퍼포먼스 때문에..
구조체를 잡을 때, 필드 순서를 조정하거나, 명시적으로 padding 넣어가면서..
아예 4-bytes 또는 8-bytes 로 적당히 align 맞춰서 잡아줍니다.
그러면 unaligned memory access 는 더 이상 문제가 아니죠.

이미 대부분의 네트워크 패킷들이 이런 형태로 만들어져 모든 플랫폼에 돌아다니고 있습니다.
여기에 사용자 구조체가 더 추가된다고 해서 특별히 더 문제될 이유는 없다는 것입니다.

어쨌든, 맨 마지막에 말씀하신 문장 때문에 한 번 더 답글을 달게 되었는데..
사용자 데이터는 프로젝트 성격이나 다루는 데이터 특성에 맞게 취사 선택하면 될 뿐..
옳다/그르다의 가치 판단 문제로 생각할 수는 없다는 뜻입니다.

되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』

Stephen Kyoungwon Kim@Google의 이미지

마지막 문장은 본문에 설계 조건이 없어 보여서 적었던 것인데, 다시 생각해 보니, 이를테면, 웹 서버라거나, 안드로이드-아이폰 앱에 서비스를 하는 서버 같은 것이었다면 높은 확률로 본문 같은 코드도 없었을 텐니, 암묵적으로 저런 코드가 사용되는 응용 영역이라는 걸 저 빼고 다들 전제하신 것이었겠네요.

패딩에 관한 추가적인 설명 감사합니다. 편안한 하루 되세요.

Stephen Kyoungwon Kim@Google의 이미지

https://stackoverflow.com/questions/1577161/passing-a-structure-through-sockets-in-c

위의 링크를 참조하세요. 바이트 포맷으로 보내면 여러 가지 문제가 있습니다. 예컨대 윈도우즈에서 struct X 객체 y를 덤프한 게, 리눅스에서 덤프한 것과 다릅니다. 들어오는 스트림도 다르구요. 받는 버퍼의 메모리 레이아웃에 따라, 그리고 받는 시스템에 따라 alignment 때문에 메모리 에러가 생길 수도 있습니다.

kysu5095의 이미지

답변 감사합니다.
alignment라는 것도 공부해봐야겠습니다. 생각보다 너무 모르고 있었네요 감사합니다!!

Stephen Kyoungwon Kim@Google의 이미지

얼라인먼트와 엔디안을 공부하시는 것은 물론 좋습니다. 구현을 바꾸는 것도 생각해 보세요. 인-메모리 데이터를 덤프해서 바이트 스트림으로 보내는 게 좋은 디자인인지 저는 확신이 안 됩니다. 그렇게 해도 되는 상황이 있을 수 있겠지만, 일반적으로는 그렇게 안 할 것 같구요.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.