Unit 56. 구조체 비트 필드 사용하기

구조체 비트 필드를 사용하면 구조체 멤버를 비트 단위로 저장할 수 있다. CPU나 기타 칩의 플래그를 다루는 저수준 프로그래밍에서 비트 단위로 값을 가져오거나 저장하는 경우가 많아 구조체 비트 필드가 유용하게 사용된다.

56.1 구조체 비트 필드를 만들고 사용하기

C99를 제외한 대부분의 컴파일러에서는 모든 정수 자료형을 사용할 수 있으며 부호 없는(unsigned)자료형을 주로 사용한다. 실수 자료형은 비트 필드로 사용할 수 없다.

비트 필드는 멤버를 선언할 때 클론 뒤에 비트 수를 지정해주면 된다.

다음은 구조체를 7비트, 3비트, 1비트로 나눠 비트 필드를 정의한 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
 
struct Flags{
    unsigned int a : 1;
    unsigned int b : 3;
    unsigned int c : 7;
};
 
int main()
{
    struct Flags f1;
 
    f1.a = 1;   // 0000 0001
    f1.b = 15;  // 0000 1111
    f1.c = 255// 1111 1111
 
    printf("%u\n", f1.a);
    printf("%u\n", f1.b);
    printf("%u\n", f1.c);
 
    return 0;
}
cs

구조체를 정의할 때 멤버 뒤에 클론을 붙여서 비트수를 지정한다.

비트 필드에 각각 1비트, 4비트, 8비트로 지정한 비트수보다 많은 비트수를 할당하였다.

각 멤버를 출력해보면 할당한 값과 다른 값이 나오는 것을 확인할 수 있다. 비트 필드에는 지정한 비트 수만큼 저장되고 나머지 비트는 버려진다. 따라서 8비트인 255는 비트 하나가 버려져서 111 1111이 되서 127이 출력됨을 확인할 수 있다.

비트 필드의 각 멤버는 최하위 비트부터 차례대로 배치된다. 따라서 a가 최하위 비트에 오고 나머지 멤버들은 상위 비트에 배치된다.

sizeof 연산자로 Flags 비트 필드 구조체의 크기를 구해보면 멤버를 unsigned int로 선언했기 때문에 4가 나온다.

56.2 비트 필드와 공용체를 함께 사용하기

CPU나 칩에 값을 설정할 대는 모든 비트를 묶어서 한꺼번에 저장한다. 비트필드와 공용체를 함께 사용하여 비트 필드의 값을 한꺼번에 사용할 수 있다.

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
#include <stdio.h>
 
struct Flags{
    union{
        struct{
            unsigned short a : 3;
            unsigned short b : 2;
            unsigned short c : 7;
            unsigned short d : 4;
        };
        unsigned short e;
    };
};
 
int main()
{
    struct Flags f1 = {0, };
 
    f1.a = 4;   // 0000 0100
    f1.b = 2;   // 0000 0010
    f1.c = 80;  // 0101 0000
    f1.d = 15;  // 0000 1111
 
    printf("%u\n", f1.e);
 
    return 0;
}
cs

비트 필도로 사용할 멤버는 익명 구조체로 감싸주고, 비트 필드의 값을 한번에 접근할 수 있는 unsigned short형 멤버(e)를 선언하여 익명 공용체로 감싸준다. 각 비트 필드에 값을 할당하고, e를 출력하면 64020이 나온다. 각 비트 필드에 할당한 비트들을 차례로 연결하면 1111 1010000 10 100이 되어 10진수로 64020이 된다.

56.3 퀴즈

정답은 d 이다.

정답은 4이다.

정답은 e 이다.

56.4 연습문제 : 구조체로 플래그 비트 필드 만들기

비트 수를 제한해야 하므로 정답은 다음 코드와 같다.

unsigned int a : 2;
unsigned int b : 1;
unsigned int c : 6;

56.5 연습문제 : 구조체와 공용체로 플래그 비트 필드 만들기

32936을 2진수로 표현하면 1000 0000 1010 1000이다. 할당할 값들을 2진수로 표현하면 각각 1000, 10, 10, 1000 0000 이므로 비트를 맞춰주면 다음과 같다.

unsigned short a : 4;
unsigned short b : 2;
unsigned short c : 2;
unsigned short d : 8;

56.6 심사문제 : 구조체로 플래그 비트 필드 만들기

정답은 다음 코드와 같다.

struct Flags{
  unsigned int a : 4;
  unsigned int b : 7;
  unsigned int c : 3;
};

56.7 심사문제 : 구조체와 공용체로 플래그 비트 필드 만들기

57412를 2진수로 변환하면 1110 0000 0100 0100이므로 비트수를 맞춰주면 다음과 같다.

unsigned int a : 3;
unsigned int b : 4;
unsigned int c : 7;
unsigned int d : 2;

Unit 57. 열거형 사용하기

열거형은 정수형 상수에 이름을 붙여서 코드를 이해하기 쉽도록 해준다.

57.1 열거형 정의하기

열거형은 eum키워드로 정의하고, enum 열거형이름 변수이름; 의 형태로 정의하여 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
 
enum DayOfWeek{
    Sunday = 0,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
};
 
int main()
{
    enum DayOfWeek week;
    week = Tuesday;
 
    printf("%d\n", week);
    return 0;
}
cs

enum 키워드 뒤에 열거형 이름을 지정하고, 중괄호안에 값을 나열한다. 각 값들은 콤마로 구분하고, 할당연산자를 사용하여 값을 할당할 수 있다. 열거형의 값은 처음에만 할당해주면 그 아래에 오는 값들은 1씩 증가하며 자동으로 할당된다.

정의한 열거형을 사용하려면 열거형 변수를 선언해야 한다. 열거형 이름 앞에 enum 키워드를 붙여준다. 보통 열거형 변수에는 미리 정의한 열거형 값을 넣는다.

연속되지 않은 불규칙한 값을 사용하려면 모든 열거형 값에 정수를 할당하면 된다.

57.2 열거형을 switch에 활용하기

열거형은 다음과 같이 switch 분기문을 사용할 때 유용하게 사용할 수 있다.

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
31
32
33
34
#include <stdio.h>
 
enum LuxSkill{
    LightBinding = 1,
    PrismaticBarrier,
    LucentSigularity,
    FinalSpark
};
 
int main()
{
    enum LuxSkill skill;
    skill = LightBinding;
 
    switch (skill)
    {
    case LightBinding:
        printf("LightBinding\n");
        break;
    case PrismaticBarrier:
        printf("PrismaticBarrier\n");
        break;
    case LucentSigularity:
        printf("LucentSigurlarty\n");
        break;
    case FinalSpark:
        printf("FinalSpark\n");
        break;
    default:
        break;
    }
 
    return 0;
}
cs

열거형 변수를 switch문에 사용하면 열거형 값에 따라 코드를 실행할 수 있다. 위 코드에서는 skill 에 LightBinding를 할당했으므로 이에 해당하는 case가 실행된다.

57.3 열거형을 for에 활용하기

다음과 같이 열거형을 정의한 뒤 for 반복문에 활용할 수 도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
 
typedef enum _DayOfWeek { 
    Sunday = 0,                  
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    DayOfWeekCount      // 열거형 값의 개수
} DayOfWeek;                 
 
int main()
{
    
    for (DayOfWeek i = Sunday; i < DayOfWeekCount; i++
    {
        printf("%d\n", i);
    }
 
    return 0;
}
cs

열거형을 정의할 때 초기값을 0으로 할당하였고, 열거형 값을 나열하다 마지막에 열거형 값의 개수를 나타내는 항목을 넣어주었다. typedef를 사용하여 열거형 별칭을 DayOfWeek로 정의하였다.

for 반복문에서 열거형 별칭으로 변수 i를 선언한 뒤 초기값으로 Sunday를 넣고 i가 DayOfWeekCount보다 작을 때 까지 반복하여 0(Sunday) 부터 6(Saturday)까지 반복할 수 있다.

57.4 퀴즈

정답은 d이다.

정답은 4이다.

정답은 e이다.

정답은 c이다.

57.5 연습문제 : 장치 종류 정의하기

두번째 열거형을 출력했는데 3이 나왔으므로 정답은 2이다.

57.6 연습문제 : 게임 캐릭터 스킬 처리하기

정답은 다음과 같다.

1. switch(skill)

2. Tumble

3. SilverBolts

4. Condemn

5. FinalHour

57.7 연습문제 : 월 출력하기

정답은 다음 코드와 같다.

Jan = 1,
Feb,
Mar,
Apr,
May,
Jun,
Jul,
Aug,
Sep,
Oct,
Nov,
Dec,
MonthCount

값을 1부터 할당하고 마지막 항목은 값의 개수를 나타내는 항목을 만들면 된다.

57.8 심사문제 : 프로토콜 종류 정의하기

정답은 다음 코드와 같다.

enum PROTOCOL_TYPE{
    PROTOCOL_IP = 4,
    PROTOCOL_UDP,
    PROTOCOL_TCP
};

57.9 심사문제 : 게임 캐릭터 스킬 처리하기

정답은 다음 코드와 같다.

enum MasterYiSkill skill;
skill = Meditation;

57.10 심사문제 : 인터페이스 타입 출력하기

정답은 다음 코드와 같다.

for(INTERFACE_TYPE i = Internal; i < MaximumInterfaceType; i++)

Unit 58. 자료형 변환하기

C에서는 자료형이 같거나 크기가 큰 쪽, 표현 범위가 넓은 쪽으로 저장하면 자동으로 형 변환이 된다. 그러나 자료형이 다르면서 크기가 작은쪽, 표현 범위가 좁은 쪽으로 저장하면 컴파일 경고가 발생할 수 있다. 자료형의 크기가 큰 쪽, 표현 범위가 넓은 쪽으로 자동 형 변환 되는 것을 형 확장 이라 하며 암시적 형 변환이라 한다. 반대로 자료형 크기가 작은 쪽, 표현 범위가 좁은 쪽으로 변환되는것을 형 축소라 한다. 형 축소에서 컴파일 경고가 나오지 않도록 만드는 것을 형 변환 이라 한다. 프로그래머가 강제적으로 자료현을 변환한다고 하여 명시적 형 변환이라 부르기도 한다. 형 확장은 값의 손실이 없기 때문에 컴파일러가 알아서 처리할 수 있지만 형 축소는 값의 손실이 발생하여 컴파일러가 알아서 처리할 수 없기 때문에 경고가 발생한다. 형 변환은 컴파일러에게 자료형을 변환한다는 의도를 명확하게 알려주는 것이다.

58.1 기본 자료형 변환하기

자료형을 지정하여 변환하는 것을 명시적 자료 변환이라고 하며 변수나 값 앞에 변환할 자료형을 붙인 뒤 괄호로 묶어준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
 
int main()
{
    int num1 = 32;
    int num2 = 7;
    float num3;
 
    num3 = num1 / num2;     
    printf("%f\n", num3);    
 
    num3 = (float)num1 / num2;    
    printf("%f\n", num3);         
 
    return 0;
}
cs

정수 / 정수를 계산하게 되면 정수 4가 나와서 num3에는 4.000000이 저장된다. num1을 float로 강제 변환해주면 실수 / 정수가 되어 결과도 실수가 되어 num3에는 4.571429가 저장되며 컴파일 경고도 발생하지 않는다.

58.2 포인터 변환하기

다음 코드는 포인터끼리 변환하는 예시이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <stdlib.h>   
 
int main()
{
    int *numPtr = malloc(sizeof(int));  
    char *cPtr;
 
    *numPtr = 0x12345678;
    cPtr = (char *)numPtr;
 
    printf("0x%x\n"*cPtr);   
 
    free(numPtr);   
    return 0;
}
cs

numPtr에 메모리를 할당하고 역참조 하여 0x12345678을 저장하였다. cPtr에 int 포인터를 char 포인터로 저장하여 메모리 주소를 저장하면 cPtr은 char 포인터이므로 1바이트만 값을 가져오게 된다. 메모리 공간에는 리틀 엔디언 형식으로 값이 저장되므로 출력값은 0x78이 된다.

위와 반대로 크기가 작은 메모리 공간을 할당한 뒤 큰 자료형의 포인터로 역참조하면 의도치 않은 값을 가져올 수도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <stdlib.h>   
 
int main()
{
    short *numPtr1 = malloc(sizeof(short));    
    int *numPtr2;
 
    *numPtr1 = 0x1234;
    numPtr2 = (int *)numPtr1;    
    
    printf("0x%x\n"*numPtr2);    
    free(numPtr1);  
    return 0;
}
cs

크기가 작은 메모리를 할당한 뒤 큰 자료형의 포인터로 역참조하면 컴파일러에 따라 옆의 메모리 공간을 침범하여 값을 가져올 수 도 있다. 위 코드는 2바이트 만큼 메모리를 할당했으므로 0x1234만 저장되어 있지만 4바이트 크기만큼 값을 가져오면 2바이트 크기를 벗어나서 malloc함수로 할당하지 않은 공간까지 함께 가져올 수도 있다. 할당하지 않은 공간에는 쓰레기값이 들어있다.

포인터를 다른 자료형으로 변환하면서 역참조 하려면 다음과 같이 괄호 앞에 역참조 연산자를 붙여주면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <stdlib.h> 
 
int main()
{
    int *numPtr = malloc(sizeof(int));   
    char *cPtr;
 
    *numPtr = 0x12345678;
 
    printf("0x%x\n"*(char *)numPtr);    
    free(numPtr);  
    return 0;
}
cs

*(char *)numPtr같이 char 포인터로 변환한 뒤 역참조 하여 1바이트 크기만큼 0x78이 출력되었다.

58.3 void포인터 변환하기

void포인터는 자료형이 정해져 있지 않으므로 역참조 연산을 할 수 없다. 하지만 void포인터를 다른 자료형으로 변환하면 역참조를 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
 
int main()
{
    int num1 = 10;
    float num2 = 3.5f;
    char c1 = 'a';
    void *ptr;
 
    ptr = &num1;   
    printf("%d\n"*(int *)ptr);    
 
    ptr = &num2;   
    printf("%f\n"*(float *)ptr);  
 
    ptr = &c1;    
    printf("%c\n"*(char *)ptr);   
 
    return 0;
}
cs

ptr은 void 포인터이기 때문에 역참조를 하면 컴파일 에러가 발생하지만 void 포인터를 다른 자료형으로 변환하여 역탐조를 하게 되면 컴파일 에러가 발생하지 않는다.

58.4 구조체 포인터 변환하기

자료형 변환을 주로 사용하는 상황은 구조체 포인터를 변환할 때 이다. struct 구조체 이름 뒤에 *을 붙여주고 괄호로 묶으면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <stdlib.h>    
struct Data {
    char c1;
    int num1;
};
 
int main()
{
    struct Data *d1 = malloc(sizeof(struct Data));    
    void *ptr;   
 
    d1->c1 = 'a';
    d1->num1 = 10;
 
    ptr = d1;  
 
    printf("%c\n", ((struct Data *)ptr)->c1);      
    printf("%d\n", ((struct Data *)ptr)->num1);    
 
    free(d1);    
    return 0;
}
cs

구조체 포인터 d1을 선언하고 구조체 크기만큼 메모리를 할당하여 각 멤버에 'a'와 10을 저장하였다. 이후 void ptr에 d1을 할당하였다. void포인터이기 때문에 Data 구조체의 형태를 모르는 상태 이므로 멤버에 바로 접근할 수 없기 때문에 위와 같이 ptr을 Data 구조체로 변환한 뒤 멤버에 접근해야 한다. ptr을 구조체 포인터로 변환한 뒤 멤버에 접근할 때는 자료형 변환과 포인터 전체를 다시 한 번 괄호로 묶어야 -> 연산자를 사용하여 구조체 멤버에 접근할 수 있다.

58.5 퀴즈

정답은 d 이다.

정답은 e이다.

정답은 c이다.

58.6 연습문제 : 삼각형의 넓이 구하기

base와 height가 모두 정수이므로 하나를 실수로 변환해야 한다. 정답은 다음 코드와 같다.

area = (float)base * height / 2;

58.7 포인터 변환하기

출력값이 2바이트 이므로 short 포인터로 형변환 해야 한다.

numPtr2 = (short *)nupPtr1;

58.8 연습문제 : void 포인터 변환하기

numPtr1의 크기가 uint64_t이므로 uint64_t 포인터로 변환한 뒤 역참조하여야 한다.

*(uint64_t *)ptr

58.9 연습문제 : 구조체 포인터 변환하기

정답은 다음과 같다.

((struct Person *)ptr)->name, ((struct Person *)ptr)->age

58.10 심사문제 : 소수점 이하 버리기

num1이 실수이고, 출력하는 것은 num2이고, 정수이므로 num2에 num1을 넣으면 된다.

num2 = num1;

58.11 심사문제 : 포인터 변환하기

입력받은 값을 형변환 하여 numPtr2에 넣으면 된다.

numPtr2 = (unsigned int *)numPtr1;

58.12 심사문제 : void 포인터 변환하기

void 포인터를 numPtr의 포인터형과 동일하게 변환하면 된다.

*(long double *)

58.13 심사문제 : 구조체 포인터 변환하기

void 포인터이므로 Stats 구조체 포인터로 변환하여 각 멤버에 접근해야 한다.

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

Unit 59. 포인터 연산 사용하기

포인터 변수에는 메모리 주소가 들어있다. 메모리 주소에 일정 숫자를 더하거나 빼면 메모리 주소가 증가, 감소한다. 포인터 연산을 통해 다른 메모리 주소에 접근할 수 있으며 메모리 주소를 손쉽게 옮겨 다닐 수 있다. 메모리 주소가 커지는 상황을 순방향으로 이동, 메모리 주소가 작아지는 상황을 역방향으로 이동이라고 한다.

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

포인터 변수에 +, - 사용하여 값을 더하거나 빼고, ++, -- 연산자로 값을 증가, 감소시킬 수 있다. *, / 연산자와 실수값은 사용할 수 없다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
 
int main()
{
    int numArr[5= { 1122334455 };
    int *numPtrA;
    int *numPtrB;
    int *numPtrC;
 
    numPtrA = numArr;   
 
    numPtrB = numPtrA + 1;   
    numPtrC = numPtrA + 2;    
    
    printf("%p\n", numPtrA);   
    printf("%p\n", numPtrB);  
    printf("%p\n", numPtrC);    
 
    return 0;
}
cs

정수형 배열을 선언하고, 배열을 포인터에 할당하였다. 메모리 주소에 1씩 더하면 포인터 자료형의 크기만큼 더하거나 빼져서 위의 경우 int형 배열이였기 때문에 4씩 더해졌다.

뺄셈에서도 int형 배열이기 때문에 1을 뺄 때 마다 int형 크기인 4바이트만큼 역방향으로 이동한다.

증가 감소 연산도 1씩 더하거나 빼는 것이기 때문에 연산을 수행할 때 마다 자료형의 크기만큼 더하거나 빼진다.

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

포인터 연산으로 조작한 메모리 주소도 역참조 연산으로 메모리에 접근할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
 
int main()
{
    int numArr[5= { 1122334455 };
    int *numPtrA;
    int *numPtrB;
    int *numPtrC;
 
    numPtrA = numArr;    
    numPtrB = numPtrA + 1;    
    numPtrC = numPtrA + 2;   
 
    printf("%d\n"*numPtrB);   
    printf("%d\n"*numPtrC);   
    return 0;
}
cs

numPtrB는 numPtrA에 1을 더해 4바이트 만큼 순방향 이동했고, numPtrC는 numPtrA에 2를 더해서 8바이트만큼 순방향으로 이동했다. numPtrB, numPtrC도 일반 포인터이므로 역참조 연산이 가능하고, 두 값은 결국 numArr[1], numArr[2]와 같다.

다음과 같이 포인터 연산을 한 부분을 괄호로 묶어 맨 앞에 역참조 연산자를 붙여 포인터 연산과 역참조 연산을 동시에 사용할 수도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
 
int main()
{
    int numArr[5= { 1122334455 };
    int *numPtrA;
 
    numPtrA = numArr;  
 
    printf("%d\n"*(numPtrA + 1));    
    printf("%d\n"*(numPtrA + 2));   
    return 0;
}
cs

만약 포인터 연산을 괄호로 묶어주지 않으면 역참조 연산자가 먼저 실행되어 값을 가져온 뒤 연산을 하게 된다.

증가연산자와 감소연산자를 사용하고 역참조 연산자를 사용할 수 도 있다. 이때 증가, 감소 연산자를 변수 뒤에 붙이고 포인터 연산을 하면 현재 메모리의 값을 가져온뒤 포인터 연산을 하기 때문에 주의해야 한다.

59.3 void 포인터로 포인터 연산하기

void 포인터는 자료형의 크기가 정해져있지 않아서 +, - 로 연산을 해도 얼마만큼 이동할지 알 수 없기에 포인터 연산을 할 수없다.

만약 void 포인터로 포인터 연산을 하고 싶다면 다른 포인터로 변환한 뒤 연산을 하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <stdlib.h> 
 
int main()
{
    void *ptr = malloc(100);   
 
    printf("%p\n", ptr);               
    printf("%p\n", (int *)ptr + 1);    
    printf("%p\n", (int *)ptr - 1);   
 
    free(ptr);
    return 0;
}
 
cs

void 포인터를 int 포인터로 변환하여 포인터 연산을 하였다.

void 포인터를 포인터 연산한 뒤 역참조 하려면 먼저 다른 포인터로 변환하여 포인터 연산을 하고, 연산 부분을 괄호로 묶어 맨 앞에 역참조 연산자를 붙이면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
 
int main()
{
    int numArr[5= { 1122334455 };
    void *ptr = &numArr[2];  
 
    printf("%d\n"*(int *)ptr);
   
 
    printf("%d\n"*((int *)ptr + 1));   
    printf("%d\n"*((int *)ptr - 1));    
    return 0;
}
 
cs

void 포인터 ptr을 int 포인터로 변환하여 포인터 연산 후 역참조를 하였다.

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

구조체 포인터도 포인터 연산을 할 수 있다. 포인터 연산을 한 부분을 괄호로 묶고, 화살표 연산자를 사용하여 멤버에 접근하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
 
struct Data {
    int num1;
    int num2;
};
 
int main()
{
    struct Data d[3= { { 1020 }, { 3040 }, { 5060 } };    
    struct Data *ptr;   
    ptr = d;  
 
    printf("%d %d\n", (ptr + 1)->num1, (ptr + 1)->num2);  
    printf("%d %d\n", (ptr + 2)->num1, (ptr + 2)->num2);   
    return 0;
}
cs

구조체 배열 d를 선언한 뒤 첫번째 요소의 메모리 주소를 ptr에 저장하였다. (ptr+1)->num1 과 같이 포인터 연산을 한뒤 괄호로 묶은 후 화살표 련산자를 사용하여 멤버에 접근할 수 있다. 이는 구조체 배열의 인덱스로 접근하는 d[1].num1과 같다. 구조체의 크기는 4바이트짜리 int형 멤버가 두 개 있으므로 8바이트이다. 따라서 포인터 연산을 하게 되면 8바이트씩 메모리 주소에서 더하거나 뺀다.

다음은 동적 메모리를 할당하여 포인터 연산을 하는 것이다.

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
31
#include <stdio.h>
#include <stdlib.h>   
#include <string.h>    
 
struct Data {
    int num1;
    int num2;
};
 
int main()
{
    void *ptr = malloc(sizeof(struct Data) * 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;    
 
    ((struct Data *)ptr + 2)->num1 = 50;    
    ((struct Data *)ptr + 2)->num2 = 60
     
    memcpy(d, ptr, sizeof(struct Data) * 3);   
    
    printf("%d %d\n", d[1].num1, d[1].num2);    
    printf("%d %d\n", ((struct Data *)ptr + 2)->num1, ((struct Data *)ptr + 2)->num2);
                                               
    free(ptr);   
    return 0;
}
cs

구조체를 포인터로 변환한 뒤 값을 더해주면 된다. 이후 포인터 연산을 하여 메모리에 값을 저장하는데, 구조체 포인터로 변환하지 않는다면 void 포인터이기 때문에 구조체 형태를 몰라서 컴파일 에러를 발생한다. 

구조체 배열의 멤버를 출력해보면 포인터 연산을 통해 저장한 값이 출력됨을 알 수 있다. 동적 메모리에 저장한 값의 위치가 구조체 배열의 형태와 같고, 동적 메모리의 내용을 그대로 복사했기 때문이다.

 

59.5 퀴즈

정답은 b,c,e,f이다.

정답은 d이다.

정답은 d, g 이다.

정답은 e 이다.

 

59.6 연습문제 : 포인터 연산으로 메모리 주소 출력하기

int 형 포인터이므로 4바이트씩 포인터 연산을 하므로 각 2와 5씩 더하면 된다. 답은 +2, +5이다.

 

59.7 연습문제 : 포인터 연산과 역참조 사용하기

정답은 다음 코드와 같다.

*(numPtrA + 2)
((int *)ptr + 1)

 

59.8 연습문제 : 구조체 포인터와 포인터 연산

정답은 다음과 같다.

(ptr + 1)->x, (ptr + 2)->y

 

59.9 심사문제 : 포인터 연산으로 메모리 주소 조작하기

short 자료형으로 2바이트 이기 때문에 각 3과 5를 더해야 한다.

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

 

59.10 심사문제 : 포인터 연산과 역참조 사용하기

정답은 다음 코드와 같다.

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

 

59.11 심사문제 : 구조체 포인터로 포인터 연산하기

정답은 다음 코드와 같다.

result1 = ((struct Point3D *)ptr + 1) -> x;
result2 = ((struct Point3D *)ptr + 2) -> z;

 

+ Recent posts