Coding/C

[C언어] 자료형 변환, 포인터 연산 - 코딩도장

GunP4ng 2024. 1. 30. 23:12

[코딩도장] Unit 58 ~ 59


1. 자료형 변환

1. 기본 자료형 변환하기

C언어는 자료형이 같거나 크기가 큰 쪽으로 저장하면

자료형이 자동으로 변환된다

 

※ 자료형이 다르면서 크기가 작은 쪽으로 변환하면 컴파일 경고가 발생한다

 

자료형을 크기가 큰 쪽으로 자동 변환하는 것을 형 확장이라 한다

반대로 크기가 작은 쪽으로 변환되는 것을 형 축소라고 한다

 

형 축소가 발생할 때 컴파일 경고가 뜨지 않도록 만드는 것을 형 변환이라고 한다

자료형을 지정하여 변환하는 것을 명시적 형 변환이라 한다

 

자료형을 변환하려면 변수나 값 앞에 변환할 자료형을 ( ) 로 묶어주면 된다

  • (자료형) 변수
int num1 = 32;
int num2 = 7;
float num3;

num3 = num1 / num2;      // 컴파일 경고 발생
printf("%f\n", num3);    // 4.000000

num3 = (float)num1 / num2;    // num1을 float로 변환
printf("%f\n", num3);         // 4.571429

num1 을 float 으로 변환하면 컴파일 경고가 뜨지 않는다

 

2. 포인터 변환하기

포인터의 자료형을 변환하려면 자료형 뒤에 * 를 붙여 ( ) 로 묶어주면 된다

  • (자료형 *)포인터
int *numPtr = malloc(sizeof(int));    // 4바이트만큼 메모리 할당
char *cPtr;

*numPtr = 0x12345678;

cPtr = (char *)numPtr;     // int 포인터 numPtr을 char 포인터로 변환. 메모리 주소만 저장됨

printf("0x%x\n", *cPtr);   // 0x78: 낮은 자릿수 1바이트를 가져오므로 0x78

int 포인터를 char 포인터로 변환하여 메모리 주소를 저장한다

cPtr 은 char 포인터이므로 1바이트만큼 값을 가져온다

포인터 안에 저장된 메모리 주소는 같지만 자료형에 따라  값을 가져오는 크기가 결정된다

 

포인터 변환

1바이트 char 크기만큼 낮은 자릿수의 값을 가져오므로 0x78이 된다

 

void 포인터는 자료형이 정해져 있지 않으므로 역참조 연산을 할 수 없다

※ void 포인터는 다른 자료형으로 변환한 후 역참조 해야한다

 

3. 구조체 포인터 변환하기

구조체 포인터는 struct 와 구조체 이름 뒤에 * 를 붙이고 ( ) 로 묶어주면 된다

  • (struct 구조체이름 *)포인터
  • ((struct 구조체이름 *)포인터)->멤버
struct Data *d1 = malloc(sizeof(struct Data));    // 포인터에 구조체 크기만큼 메모리 할당
void *ptr;    // void 포인터 선언

d1->c1 = 'a';
d1->num1 = 10;

ptr = d1;    // void 포인터에 d1 할당. 포인터 자료형이 달라도 컴파일 경고가 발생하지 않음.

printf("%c\n", ((struct Data *)ptr)->c1);      // 'a' : 구조체 포인터로 변환하여 멤버에 접근
printf("%d\n", ((struct Data *)ptr)->num1);    // 10  : 구조체 포인터로 변환하여 멤버에 접근

ptr 은 void 포인터라 구조체의 형태를 모르기 때문에 멤버에 접근할 수 없다

((struct Data *)ptr)->c1 같이 ptr 을 Data 구조체 포인터로 변환하면 멤버에 접근할 수 있다

구조체 포인터 변환과 -> 연산자

ptr 을 구조체 포인터로 변환한 뒤 멤버에 접근하려면

자료형 변환과 포인터 전체를 괄호로 묶어준 뒤 멤버에 접근해야 한다

 

 

2. 포인터 연산

1. 포인터 연산 사용하기

포인터도 변수이기 때문에 값을 증가시키거나 뺄 수 있다

※ 연산하는 값이 메모리 주소이기 때문에 곱셈이나 나눗셈은 불가능하다

 

int numArr[5] = { 11, 22, 33, 44, 55 };
int *numPtrA;
int *numPtrB;
int *numPtrC;

numPtrA = numArr;    // 배열 첫 번째 요소의 메모리 주소를 포인터에 저장

numPtrB = numPtrA + 1;    // 포인터 연산
numPtrC = numPtrA + 2;    // 포인터 연산
    
printf("%p\n", numPtrA);    // 00A3FC00: 메모리 주소. 컴퓨터마다, 실행할 때마다 달라짐
printf("%p\n", numPtrB);    // 00A3FC04: sizeof(int) * 1이므로 numPtrA에서 4가 증가함
printf("%p\n", numPtrC);    // 00A3FC08: sizeof(int) * 2이므로 numPtrA에서 8이 증가함

포인터 연산은 포인터 자료형의 크기만큼 더하거나 뺀다

numPtrA 는 4바이트 크기의 int 형이다

numPtrA + 1 은 메모리 주소에서 4바이트만큼 이동한다는 뜻이다

포인터 덧셈 연산과 메모리 주소

 

2. void 포인터 연산

void 포인터는 자료형의 크기가 정해져 있지 않기 때문에 포인터 연산을 할 수 없다

void *ptr = malloc(100);    // 100바이트만큼 메모리 할당

printf("%p\n", ptr);               // 00FADD20: 메모리 주소. 컴퓨터마다, 실행할 때마다 달라짐
printf("%p\n", (int *)ptr + 1);    // 00FADD24: 다른 포인터로 변환한 뒤 포인터 연산
printf("%p\n", (int *)ptr - 1);    // 00FADD1C: 다른 포인터로 변환한 뒤 포인터 연산

void 포인터로 연산을 하려면 다른 포인터로 변환을 해줘야 한다

 

void 포인터를 연산한 뒤 역참조 하려면

포인터 연산 부분을 ( ) 로 묶어준 뒤 맨 앞에 * 를 붙이면 된다

int numArr[5] = { 11, 22, 33, 44, 55 };
void *ptr = &numArr[2];    // 두 번째 요소의 메모리 주소

printf("%d\n", *((int *)ptr + 1));    // 44

 

3. 구조체 포인터 연산

struct Data d[3] = { { 10, 20 }, { 30, 40 }, { 50, 60 } };    // 구조체 배열 선언과 값 초기화
struct Data *ptr;    // 구조체 포인터 선언

ptr = d;    // 구조체 배열 첫 번째 요소의 메모리 주소를 포인터에 저장

printf("%d %d\n", (ptr + 1)->num1, (ptr + 1)->num2);    // 30 40: 구조체 배열에서 멤버의 값 출력
                                                        // d[1].num1, d[1].num2와 같음

printf("%d %d\n", (ptr + 2)->num1, (ptr + 2)->num2);    // 50 60: 구조체 배열에서 멤버의 값 출력
                                                        // d[2].num1, d[2].num2와 같음

구조체 포인터는 (ptr + 1)->num1 과 같이 포인터 연산을 괄호로 묶어준다

-> 연산자를 사용하면 멤버에 접근할 수 있다

(ptr + 1)->num1 은 d[1].num1 과 같다

 

구조체 포인터 연산

구조체 Data 의 크기는 int 형 멤버가 2개이므로 8바이트이다

포인터 연산을 하면 8바이트씩 더하거나 뺀다

 

void *ptr = malloc(sizeof(struct Data) * 3);    // 구조체 3개 크기만큼 동적 메모리 할당
struct Data d[3];

((struct Data *)ptr)->num1 = 10;        // 포인터 연산으로 메모리에 값 저장
((struct Data *)ptr)->num2 = 20;        // 포인터 연산으로 메모리에 값 저장

((struct Data *)ptr + 1)->num1 = 30;    // 포인터 연산으로 메모리에 값 저장
((struct Data *)ptr + 1)->num2 = 40;    // 포인터 연산으로 메모리에 값 저장

ptr 은 void 포인터라 구조체의 형태를 모르기 때문에 멤버에 접근할 수 없다

((struct Data *)ptr + 1)->num1 처럼 구조체 포인터로 변환한 뒤 괄호로 묶어주면 된다

-> 화살표 연산자를 사용하려면 반드시 괄호로 묶어줘야 한다

 

 

3. 심사문제

58.10 소수점 이하 버리기

표준 입력으로 실수가 입력됩니다. 다음 소스 코드를 완성하여 입력된 실수에서 소수점 이하를 버린 결과가 출력되게 만드세요. 단, 컴파일 경고가 발생하지 않아야 합니다.

정답에는 밑줄 친 부분에 들어갈 코드만 작성해야 합니다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main()
{
    float num1;
    int num2;

    scanf("%f", &num1);

    __________________
    printf("%d\n", num2);

    return 0;
}

 

정답

num2 = (int)num1;

58.11 포인터 변환하기

표준 입력으로 8바이트 크기의 16진 정수가 입력됩니다. 다음 소스 코드를 완성하여 16진 정수의 낮은 자릿수 4바이트가 출력되게 만드세요. 단, 컴파일 경고가 발생하지 않아야 합니다.

정답에는 밑줄 친 부분에 들어갈 코드만 작성해야 합니다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

int main()
{
    unsigned long long *numPtr1 = malloc(sizeof(unsigned long long));
    unsigned int *numPtr2;

    scanf("%llx", numPtr1);

    __________________________________
    printf("0x%x\n", *numPtr2);

    free(numPtr1);

    return 0;
}

 

정답

numPtr2 = (unsigned int *)numPtr1;

58.12 void 포인터 변환하기

표준 입력으로 실수가 입력됩니다. 다음 소스 코드를 완성하여 입력된 실수가 그대로 출력되게 만드세요.

정답에는 밑줄 친 부분에 들어갈 코드만 작성해야 합니다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

int main()
{
    long double *numPtr1 = malloc(sizeof(long double));
    void *ptr;

    scanf("%Lf", numPtr1);

    ptr = numPtr1;

    printf("%Lf\n", _______________ptr);

    free(numPtr1);

    return 0;
}

 

정답

*(long double *)

58.13 구조체 포인터 변환하기

표준 입력으로 게임 캐릭터의 마나와 이동 속도가 정수로 입력됩니다. 다음 소스 코드를 완성하여 마나와 이동 속도가 각 줄에 출력되게 만드세요.

정답에는 밑줄 친 부분에 들어갈 코드만 작성해야 합니다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Stats {
    float health;
    float healthRegen;
    unsigned int mana;
    float manaRegen;
    float range;
    float attackDamage;
    float armor;
    float attackSpeed;
    float magicResist;
    unsigned int movementSpeed;
};
 
int main()
{
    void *ptr = malloc(sizeof(struct Stats));
    struct Stats st;

    scanf("%u %u", &st.mana, &st.movementSpeed);

    memcpy(ptr, &st, sizeof(struct Stats));
    st.mana = 0;
    st.movementSpeed = 0;

    ____________________________________
    ____________________________________

    free(ptr);

    return 0;
}

 

정답

printf("%u\n", ((struct Stats *)ptr)->mana);
printf("%u\n", ((struct Stats *)ptr)->movementSpeed);

59.9 포인터 연산으로 메모리 주소 조작하기

다음 소스 코드를 완성하여 입력된 메모리 주소에서 순방향으로 6바이트, 10바이트만큼 떨어진 메모리 주소가 각 줄에 출력되게 만드세요.

정답에는 밑줄 친 부분에 들어갈 코드만 작성해야 합니다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main()
{
    short *numPtrA;
    short *numPtrB;
    short *numPtrC;

    scanf("%p", &numPtrA);

    ____________________
    ____________________
 
    printf("%X\n", numPtrB);
    printf("%X\n", numPtrC);

    return 0;
}

 

정답

numPtrB = numPtrA + 3;
numPtrC = numPtrA + 5;

59.10 포인터 연산과 역참조 사용하기

표준 입력으로 정수 여섯 개가 입력되어 두 배열에 저장됩니다. 다음 소스 코드를 완성하여 입력된 정수 중 세 번째와 다섯 번째가 출력되게 만드세요.

정답에는 밑줄 친 부분에 들어갈 코드만 작성해야 합니다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    int numArr1[3] = { 0, };
    long long numArr2[3] = { 0, };
    int *numPtr = malloc(sizeof(int) * 3);
    void *ptr = malloc(sizeof(long long) * 3);
    int num1;
    long long num2;

    scanf("%d %d %d %lld %lld %lld", 
        &numArr1[0], &numArr1[1], &numArr1[2], 
        &numArr2[0], &numArr2[1], &numArr2[2]
    );

    memcpy(numPtr, numArr1, sizeof(int) * 3);
    memcpy(ptr, numArr2, sizeof(long long) * 3);
    numArr1[0] = numArr1[1] = numArr1[2] = 0;
    numArr2[0] = numArr2[1] = numArr2[2] = 0;

    __________________________
    __________________________

    printf("%d %lld\n", num1, num2);

    free(ptr);
    free(numPtr);
 
    return 0;
}

 

정답

num1 = *(numPtr + 2);
num2 = *(((long long *)ptr) + 1);

59.11 구조체 포인터로 포인터 연산하기

표준 입력으로 x, y, z 좌표 3개가 입력되어 Point3D 배열에 저장됩니다. 다음 소스 코드를 완성하여 구조체 배열에서 두 번째 요소의 x 멤버와 세 번째 요소의 z 멤버가 출력되게 만드세요.

정답에는 밑줄 친 부분에 들어갈 코드만 작성해야 합니다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Point3D {
    float x;
    float y;
    float z;
};

int main()
{
    void *ptr = malloc(sizeof(struct Point3D) * 3);
    struct Point3D p[3];
    float result1, result2;

    scanf("%f %f %f %f %f %f %f %f %f", 
        &p[0].x, &p[0].y, &p[0].z, 
        &p[1].x, &p[1].y, &p[1].z, 
        &p[2].x, &p[2].y, &p[2].z
    );

    memcpy(ptr, p, sizeof(struct Point3D) * 3);
    memset(p, 0, sizeof(struct Point3D) * 3);

    _________________________________________
    _________________________________________

    printf("%.1f %.1f\n", result1, result2);

    free(ptr);

    return 0;
}

 

정답

// 2번째 x
result1 = ((struct Point3D *)ptr + 1)->x;

// 3번째 z
result2 = ((struct Point3D *)ptr + 2)->z;