![article thumbnail image](https://blog.kakaocdn.net/dn/ubviG/btq1I7Evcm2/mhLoaVxoQtyzhmNeNI92f1/img.jpg)
Unit 58. 자료형 변환하기
58.1 기본 자료형 변환하기
자료형을 지정하여 변환하는 것을 명시적 자료형 변환(explicit type conversion, type casting)이라고 하며 변수나 값 앞에 변환할 자료형을 붙인 뒤 ( ) (괄호)로 묶어주면 된다.
- (자료형)변수
- (자료형)값
#include <stdio.h>
int main()
{
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
return 0;
}
num3 = num1 / num2;와 같이 정수 / 정수를 계산하면 정수(int) 4가 나오고 num3에는 4.0000000이 저장된다. 이때 num3은 float형이라 int와 자료형이 달라서 다음과 같이 컴파일 경고가 발생한다.
하지만 num3 = (float)num1 / num2;와 같이 num1을 float로 강제 변환해주면 실수 / 정수가 되어 결과도 실수(float)가 된다. 따라서 num3에는 4.571429가 저장되고, 컴파일 경고가 발생하지 않는다.
-> 형 변환은 자료형(타입)을 명확하게 결정할 수 있다.
58.2 포인터 변환하기
포인터끼리 변환 할 때는 자료형 뒤에 포인터를 나타내는 *(애스터리스크)를 붙여주고 괄호로 묶어주면 된다.
- (자료형 *)포인터
#include <stdio.h>
#include <stdlib.h> // malloc, free 함수가 선언된 헤더 파일
int main()
{
int *numPtr = malloc(sizeof(int)); // 4바이트만큼 메모리 할당
char *cPtr;
*numPtr = 0x12345678;
cPtr = (char *)numPtr; // int 포인터 numPtr을 char 포인터로 변환. 메모리 주소만 저장됨
printf("0x%x\n", *cPtr); // 0x78: 낮은 자릿수 1바이트를 가져오므로 0x78
free(numPtr); // 동적 메모리 해제
return 0;
}
numPtr이나 cPtr이나 안에 저장된 메모리 주소는 같지만 자료형에 따라 역참조했을 때 값을 가져오는 크기가 결정된다.
따라서 메모리 공간에 0x12345678(리틀 엔디언: 78 56 34 12)이 저장된 상태에서 1바이트 char 크기만큼 낮은 자릿수 값을 가져오므로 0x78이 된다.
자료형이 다른 포인터에 메모리 주소가 이리저리 옮겨 다닌다고 해도 메모리 공간은 그대로이고, 값을 가져오는 크기만 달라지는 것이다.
+ 포인터를 다른 자료형으로 변환하면서 역참조하려면 다음과 같이 ( ) (괄호) 앞에 역참조 연산자 *를 붙여주면 된다.
- *(자료형 *)포인터
58.3 void 포인터 변환하기
void 포인터는 자료형이 정해져 있지 않으므로 역참조 연산을 할 수 없다. 하지만 void 포인터를 다른 자료형으로 변환하면 역참조를 할 수 있다.
- *(자료형 *)void포인터
#include <stdio.h>
int main()
{
int num1 = 10;
float num2 = 3.5f;
char c1 = 'a';
void *ptr;
ptr = &num1; // num1의 메모리 주소를 void 포인터 ptr에 저장
// printf("%d\n", *ptr); // 컴파일 에러
printf("%d\n", *(int *)ptr); // 10: void 포인터를 int 포인터로 변환한 뒤 역참조
ptr = &num2; // num2의 메모리 주소를 void 포인터 ptr에 저장
// printf("%f\n", *ptr); // 컴파일 에러
printf("%f\n", *(float *)ptr); // 3.500000: void 포인터를 float 포인터로 변환한 뒤 역참조
ptr = &c1; // c1의 메모리 주소를 void 포인터 ptr에 저장
// printf("%c\n", *ptr); // 컴파일 에러
printf("%c\n", *(char *)ptr); // a: void 포인터를 char 포인터로 변환한 뒤 역참조
return 0;
}
ptr = &num1;과 같이 int형 변수 num1의 메모리 주소를 ptr에 저장했다. 하지만 ptr은 void 포인터라 역참조를 하면 컴파일 에러가 발생다. 따라서 *(int *)ptr와 같이 void 포인터를 int 포인터로 변환한 뒤 역참조를 해야 한다.
58.4 구조체 포인터 변환하기
자료형 변환을 주로 사용하는 상황은 구조체 포인터를 변환할 때다. 이때는 struct와 구조체 이름 뒤에 *을 붙여주고 괄호로 묶어주면 된다.
- (struct 구조체이름 *)포인터
- ((struct 구조체이름 *)포인터)->멤버
#include <stdio.h>
#include <stdlib.h> // malloc, free 함수가 선언된 헤더 파일
struct Data {
char c1;
int num1;
};
int main()
{
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 : 구조체 포인터로 변환하여 멤버에 접근
free(d1); // 동적 메모리 해제
return 0;
}
여기서 ptr은 void 포인터이고 d1은 Data 구조체 포인터이지만, void 포인터는 포인터 타입을 가리지 않고 다 받아들일 수 있으므로 컴파일 경고가 발생하지 않는다.
또, ptr은 void 포인터라 Data 구조체의 형태를 모르는 상태이므로 멤버에 바로 접근을 할 수가 없다. 따라서ptr을 Data 구조체 포인터로 변환한 뒤 멤버에 접근해야 한다.
만약 (struct Data *)ptr처럼 해주면 ptr을 Data 구조체 포인터로 변환을 하지만 이 상태로는 멤버에 접근할 수 없다. 즉, (struct Data *)ptr는 다른 포인터에 메모리 주소를 저장할 때만 사용할 수 있다.
ptr을 구조체 포인터로 변환한 뒤 멤버에 접근할 때는 자료형 변환과 포인터 전체를 다시 한번 괄호로 묶어준다.
Unit 59. 포인터 연산 사용하기
59.1 포인터 연산으로 메모리 주소 조작하기
포인터 연산은 포인터 변수에 +, - 연산자를 사용하여 값을 더하거나 뺀다. 또는, ++, -- 연산자를 사용하여 값을 증가, 감소시킨다.
- 포인터 + 값
- 포인터 - 값
#include <stdio.h>
int main()
{
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이 증가함
return 0;
}
실행 결과는 다음과 같다.
00A3FC00 (메모리 주소. 컴퓨터마다, 실행할 때마다 달라짐)
00A3FC04
00A3FC08
포인터 연산은 포인터 자료형의 크기만큼 더하거나 뺀다.
즉, 계산식은 "sizeof(자료형) * 더하거나 빼는 값"이 된다.
다음은 증가, 감소 연산자를 사용한 예이다.
- 포인터++
- 포인터--
- ++포인터
- --포인터
#include <stdio.h>
int main()
{
int numArr[5] = { 11, 22, 33, 44, 55 };
int *numPtrA;
int *numPtrB;
int *numPtrC;
numPtrA = &numArr[2]; // 배열 세 번째 요소의 주소를 포인터에 저장
numPtrB = numPtrA;
numPtrC = numPtrA;
numPtrB++; // 포인터 연산
numPtrC--; // 포인터 연산
printf("%p\n", numPtrA); // 00A3FC08: 메모리 주소. 컴퓨터마다, 실행할 때마다 달라짐
printf("%p\n", numPtrB); // 00A3FC0C: sizeof(int) * 1이므로 numPtrA에서 4가 증가함
printf("%p\n", numPtrC); // 00A3FC04: sizeof(int) * -1이므로 numPtrA에서 4가 감소함
return 0;
}
증가 연산자는 + 1과 같다. 따라서 sizeof(int) * 1이 되므로 numPtrA에서 4가 증가한다. 마찬가지로 감소 연산자는 - 1과 같으므로 sizeof(int) * -1이고 numPtrB에서 4가 감소한다.
59.2 포인터 연산과 역참조 사용하기
포인터 연산으로 조작한 메모리 주소도 역참조 연산을 사용하여 메모리에 접근할 수 있다.
#include <stdio.h>
int main()
{
int numArr[5] = { 11, 22, 33, 44, 55 };
int *numPtrA;
int *numPtrB;
int *numPtrC;
numPtrA = numArr; // 배열 첫 번째 요소의 주소를 포인터에 저장
numPtrB = numPtrA + 1; // 포인터 연산. numPtrA + 4바이트
numPtrC = numPtrA + 2; // 포인터 연산. numPtrA + 8바이트
printf("%d\n", *numPtrB); // 22: 역참조로 값을 가져옴, numArr[1]과 같음
printf("%d\n", *numPtrC); // 33: 역참조로 값을 가져옴, numArr[2]와 같음
return 0;
}
59.3 void 포인터로 포인터 연산하기
void 포인터는 자료형의 크기가 정해져 있지 않기 때문에 +, -로 연산을 해도 얼마만큼 이동할지 알 수가 없다. 따라서 void 포인터는 포인터 연산을 할 수 없다.
void 포인터로 포인터 연산을 하고 싶다면 다른 포인터로 변환한 뒤 연산을 하면 된다(Visual Studio, Windows).
- (자료형 *)void포인터 + 값
- (자료형 *)void포인터 - 값
- ++(자료형 *)void포인터
- --(자료형 *)void포인터
- ((자료형 *)void포인터)++
- ((자료형 *)void포인터)--
#include <stdio.h>
#include <stdlib.h> // malloc, free 함수가 선언된 헤더 파일
int main()
{
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 *ptr2 = ptr; // 메모리 주소를 변화시킬 때는 다른 포인터에 보관
printf("%p\n", ++(int *)ptr2); // 00FADD24: 다른 포인터로 변환한 뒤 포인터 연산
printf("%p\n", --(int *)ptr2); // 00FADD20: 다른 포인터로 변환한 뒤 포인터 연산
printf("%p\n", ((int *)ptr2)++); // 00FADD20: 다른 포인터로 변환한 뒤 포인터 연산
printf("%p\n", ((int *)ptr2)--); // 00FADD24: 다른 포인터로 변환한 뒤 포인터 연산
free(ptr);
return 0;
}
ptr을 int 포인터로 변환하여 포인터 연산을 한다.
증가, 감소 연산자를 변수 앞에 사용할 때는 자료형 변환 앞에 연산자를 붙이면 된다.
59.4 구조체 포인터로 포인터 연산하기
구조체 포인터도 포인터 연산을 할 수 있다. 다음과 같이 포인터 연산을 한 부분을 ( ) (괄호)로 묶어준 뒤 -> (화살표 연산자) 연산자를 사용하여 멤버에 접근하면 된다.
- (포인터 + 값)->멤버
- (포인터 - 값)->멤버
#include <stdio.h>
struct Data {
int num1;
int num2;
};
int main()
{
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와 같음
return 0;
}
구조체 배열 d를 선언한 뒤 첫 번째 요소의 메모리 주소를 ptr에 저장했다.
구조체 포인터는 (ptr + 1)->num1와 같이 포인터 연산을 한 뒤 괄호로 묶어준다. 그리고 화살표 연산자를 사용하여 멤버에 접근할 수 있다(괄호는 반드시 사용해야함).
구조체 Data의 크기는 4바이트짜리 int형 멤버가 두 개 들어있으므로 8바이트이다. 따라서 포인터 연산을 하면 8바이트씩 메모리 주소에서 더하거나 뺀다.
'Programming Languages > C' 카테고리의 다른 글
[P4C] C언어 코딩 도장 : 문제 풀이 8 (0) | 2021.04.03 |
---|---|
[P4C] C언어 코딩 도장 : UNIT 56 ~ UNIT 57 (0) | 2021.04.03 |
[P4C] C언어 코딩 도장 : UNIT 54 ~ UNIT 55 (0) | 2021.03.28 |
[P4C] C언어 코딩 도장 : 문제 풀이 7 (0) | 2021.03.28 |
[P4C] C언어 코딩 도장 : UNIT 51 ~ UNIT 53 (0) | 2021.03.28 |