Corgi Dog Bark

ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [C 언어] 형변환 _ 구조체 형변환
    뜯고 또 뜯어보는 컴퓨터/씨쁠쁠 C++ 2022. 1. 22. 20:22
    반응형

    0.  C 에서 형변환이란?


    - C 언어에서 형변환(Type casting)이란 어느 한 데이터 타입에서 다른 데이터 타입으로의 전환입니다. Data conversion 이라고도 하며, C 에서는 빈번한 형변환이 이루어져 type casting 의 의미를 집고 넘어가는게 중요하다고 생각합니다. 

    - C 언어에서는 형변환을 하기 위해 2가지의 형변환을 제공하는데,

    1. 묵시적 형변환 (implicit type casting) 
    2. 명시적 형변환 (explicit type casting) 

    2가지를 제공하고 있습니다. 이에 대해서는 밑부분에 추가 설명 하도록 하겠습니다.

     

     


     

    1. 묵시적 형변환 Implicit Type casting


    - 묵시적 형변환의 경우, 데이터 형변환이 일어날때, 본질의 의미를 흐리지 않고, 형변환이 이뤄지는 것을 의미한다. 이러한 형변환(implicit type casting) 은 변수에 담겨있는 값의 중요성이 깨지지 않을때, 그 본질이 있다고 할 수 있다. 그럼 어떻게 " 데이터 형변환이 이루어 지는데, 본질의 의미가 안깨지냐고 할 수 있냐 ??? " 라고 물어볼 수 있는데, 이는 lower data type 이 큰 data type 으로 형변환이 이뤄지며, 데이터의 유실 없이 형변환이 수행 된다 할 수 있겠다.

    - 모든 데이터 형들은 나름의 계층 구조를 가지고 있는데, 밑의 그림과 같이 제일 하단에 있는 char 과 short 에서 int -> ... -> long -> long double 순으로 계층구조를 가지고 있고, 서로 다른 데이터 구조에 대해연산 [operands] 를 수행한다 했을때, 상위 데이터 구조로 변경이 된다. 

    [DY class room 계층구조 참조.]

    - implicit type casting 은 수많은 오류의 원인이 되기도 하므로 항상 꼼꼼히 챙겨줘야 한다. 이러한 type casting 을 막기 위해, C++ 에서는 explicit 키워드를 제공하기도 한다. // double -> float, 큰값에서 작은 형 변환은 complier 에러를 초래하기도 하지만, 항상 2번 확인하는 습관을 기르자..! 

    #include <stdio.h>
    int main() {
    	int num = 1;
        char c = 'k'; /* ASCII 값 -> 107 */
        float sum;
        sum = num + c; /* char과 int가 상위 형태의 float 으로 자동 형변환 */
        printf( " sum = %f\n", sum ); /* 108 출력 */
    }

     

     

    2. 명시적 형변환 Explicit Type casting


    - 명시적 형변환의 경우, "명시"라는 단어가 붙듯이, 형변환에 대해 바꿔줄 형변환을 미리 명시하는데에 그 의미가 있다. 보통 (type_name) expression 처럼 적어주고 사용하게 된다.

    - 예를 들어 우리가 C 에서 가장 많이 접하는 예제로 . float num = (int) 32 / (int)7 을 해주면, complier 에러가 발생하면서, num=4.0 (이때는 implicit type casting 실행)으로 나오게 될 것이다. 이를 해결하기 위해, num=(float)32/ 7 을 해주어야 비로소 num=4.57... 의 값이 나오게 될 것이다. 이처럼 우리가 원하는 형변환으로 미리 명시해주는것을 명시적 형변환이라고 한다.

     

     

    2-1. 포인터의 형변환

    포인터의 형변환도 명시적 형변환과 마찬가지로 (*data type) <포인터> 의 형태를 띄게 된다. 밑의 예를 보게 되면, numptr 은 원래 int 를 가르키는 pointer 였지만, (char *) numptr 의 꼴로 char 형태로 바꿔주게 되어, 역참조 하게 되었을때 0x78 을 반환하게 된다. 여기서 "왜 0x12 ... 로 시작하면, 0x12 아니냐" 라고 물으실 수 도 있는데, intel 프로세서를 쓰는 칩 제조사는 "리틀 엔디안"이라는 데이터 값을 저장하는 방식을 쓰기 때문에 실제로 저장되는 순번은 0x78563412 이런식으로 저장되고, IBM 이나 모토로라는 빅엔디안의 방식을 쓰기에, 0x12345678 순으로 저장된다. < 너 무 깊 어 지 니 알 아 두 기 만 하 자.!>

    /* [ C 언어 코딩 도장 참고 ]  */
    
    #include <stdio.h>
    #include <stdlib.h>    
    
    int main()
    {
        int *numPtr = malloc(sizeof(int));    // 4바이트만큼 메모리 할당
        char *cPtr;
    
        *numPtr = 0x12345678;
    
        printf("0x%x\n", *(char *)numPtr);    // 0x78: numPtr1을 char 포인터로 변환한 뒤 역참조
    
        free(numPtr);    // 동적 메모리 해제
    
        return 0;
    }

     

    - 그럼 실제로 어떻게 프로그래밍적으로 포인터 형변환을 이뤄내는지 쓰임새를 살펴보자. 밑의 프로그래밍적 기법은 socket 함수에서 주소체계를 정의하게 되면, sockaddr_in 을 보통 선언하는데, 실제로 많은 함수에서 sockaddr 로의 포인터 형변환을 많이 실시하기에 예시로 들어봤습니다.

    /*
    소켓 프로그래밍에서 주소를 전달 할때, sockaddr_in 이라는 구조체를 사용하는데, 
    다음 과 같은 구조를 띄게 된다. 
    
    */
    struct sockaddr_in
    {
    	as_family_t sin_family; 
        uint16_t sin_port; // 16비트 PORT 번호
        struct in_addr sin_addr; //32 비트 ip 주소
        char sin_zero[8]; 
    }
    
    /* 
    sockaddr_in 구조체를 생선한다음, 여러가지 형태로 전달이 되게 되는데, 보통은
    (struct sockaddr*) <sockaddr_in 포인터> 형식으로 변환을 하게 된다. 이는
    sockaddr_in 구조체 포인터를 sockaddr 구조체 포인터로 변환하는 것으로 해석이 되는데, 그럼 
    sockaddr_in 구조체를 살펴보자.
    */
    
    struct sockaddr
    {
    	sa_family_t sin_family; // 위와 동일
        char sa_data[14]; // 14 바이트 char 형 배열 
    }
    
    /* 
    위의 sockaddr_in 에서 sin_family 를 제외한 나머지 부분이 
    char 형 배열로 포인터 값으로 바뀌게 되었는데, 이는 위의 uint16_t 2바이트,
    in_addr 4 바이트, sin_zero 8 바이트 총합 14 바이트를 뜻하게 되어, 
    sockaddr_in 구조체가 sockaddr 구조체로의 형변환은 데이터 손실 없이 포인터 형변환이 이뤄지게 된다.
    */

     

    - 긴 글 읽어주셔서 감사합니다.

    반응형

    댓글

Designed by Tistory.