이 포스팅은 윤성우 님의 열혈 C를 기반으로 합니다.

C언어의 메모리

메모리는 OS에 의해 다음과 같이 4개의 영역으로 구분된다.

1. 코드 영역

2. 데이터 영역

3. 힙 영역

4. 스택 영역

1. 코드 영역 : 말 그대로 실행할 프로그램의 코드가 저장되는 공간이다. 

2. 데이터 영역 : 전역 변수, static 변수가 할당되는 공간으로 프로그램의 시작 시 메모리 공간에 할당되어 프로그램 종료 시까지 남아있게 된다.

3. 힙 영역 : 프로그래머가 원하는 시점에 변수를 할당하고 또 소멸하도록 지원을 하는 변수들이 할당되는 영역

4. 스택 영역 : 지역변수와 매개변수가 할당되는 공간 

그렇다면 이 3번 힙 영역에 대해서 자세히 알아보자.


메모리의 동적 할당

메모리 동적 할당을 배우기에 앞서서 다음의 코드를 확인해보자.

<ReadStringFault1.c>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
 
char* ReadUserName(void) {
    char name[30];
    printf("What's your name? ");
    gets(name);
    return name;
}
 
int main(void) {
    char* name1;
    char* name2;
    name1 = ReadUserName();
    printf("name1 : %s \n", name1);
 
    name2 = ReadUserName();
    printf("name2 : %s \n", name2);
    return 0;
}
cs

이 경우 ReadUserName 함수 내부에 반환되는 name은 지역변수로 스택 영역에 설정되어 있다. 따라서 함수를 빠져나가면 소멸되므로 실행이 정상적으로 되지 않는다. 그렇다면 이를 전역 변수로 바꾸면 어떻게 될까?

<ReadStringFault2.c>

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>
char name[30];
 
char* ReadUserName(void) {
    printf("What's your name? ");
    gets(name);
    return name;
}
 
int main(void) {
    char* name1;
    char* name2;
    name1 = ReadUserName();
    printf("name1 : %s \n", name1);
 
    name2 = ReadUserName();
    printf("name2 : %s \n", name2);
 
    printf("name1 : %s \n", name1);
    printf("name2 : %s \n", name2);
    return 0;
}
cs

<실행결과>

전역변수로 설정하면 위의 실행결과와 마찬가지로 name1, name2가 덮어쓰기 때문에 이름 정보가 각각 유지되지 않는다. 그렇다면 이것을 해결하기 위해서 어떻게 해야 할까? 바로 동적 할당인 malloc, free를 사용하면 된다.

malloc 즉 memory allocation 메모리 할당이며

free 즉 malloc를 통해 할당된 메모리를 소멸시켜 주는 것 이다.

그런데 malloc의 반환형을 보면 void *이다. 앞서서 void형 포인터의 경우 단순하게 받기만 할 수 있는 즉 연산은 할 수 없는 형인데 왜 void 일까? 다음의 예시를 보면 알 수 있다.

길이가 7인 int형 배열의 공간을 할당하기 위해 void * ptr = malloc(sizeof(int) * 7); 이라고 하면 사용자 입장에서는 알 수 있지만 malloc에게 전달되는 것은 malloc(28) 일 뿐이다. 따라서 malloc는 인자로 전달된 숫자만큼의 메모리 공간을 할당만 해줄 수 있고 이를 사용하기 위해 형 변환을 해야 하는 것은 사용자에게 책임을 돌린다.

따라서 int * ptr = (int *)malloc(sizeof(int)*7); 와 같은 형태로 malloc를 사용해야 한다. 예시를 살펴보자.

<DynamicMemoryAllocation.c>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <stdlib.h>
 
int main(void) {
    int* ptr = (int*)malloc(sizeof(int));
    int* ptr1 = (int*)malloc(sizeof(int)*7);
    
    *ptr = 20;
    for (int i = 0; i < 7; i++) {
        ptr1[i] = i + 1;
    }
 
    printf("%d \n"*ptr);
    for (int i = 0; i < 7; i++) {
        printf("%d ", ptr1[i]);
    }
 
    free(ptr);
    free(ptr1);
    return 0;
}
cs

<실행결과>

free 호출은 할당한 메모리를 소멸해주므로 꼭 사용해야 한다.

이제 앞서 살펴봤던 문제들을 해결해보자.

<ReadStringRight.c>

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
#include <stdio.h>
#include <stdlib.h>
 
char* ReadUserName(void) {
    char* name = (char*)malloc(sizeof(char* 30);
    printf("What's your name? ");
    gets(name);
    return name;
}
 
int main(void) {
    char* name1;
    char* name2;
    name1 = ReadUserName();
    printf("name1 : %s \n", name1);
 
    name2 = ReadUserName();
    printf("name2 : %s \n", name2);
 
    printf("again name1 : %s \n", name1);
    printf("again name2 : %s \n", name2);
 
    free(name1);
    free(name2);
    return 0;
}
cs

<실행결과>

정상적으로 작동하는 모습을 볼 수 있다.


부가적으로 realloc 함수가 있다. 이는 malloc로 할당된 영역을 재설정할 수 있게 하는 것이다.

 

'C' 카테고리의 다른 글

12. 구조체와 사용자 정의 자료형 2  (0) 2021.06.27
11. 구조체와 사용자 정의 자료형  (0) 2021.06.27
10. 문자열 관련 함수  (0) 2021.06.27
9. 함수 포인터 & void 포인터  (0) 2021.06.26
8. 2차원 배열과 포인터  (0) 2021.06.26
typedef

typedef를 배우기 전에 다음의 코드를 살펴보자. 

<소스코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
 
struct point {
    int xpos;
    int ypos;
};
 
int main(void) {
    struct point pnt = { 23 };
    printf("[%d, %d]", pnt.xpos, pnt.ypos);
 
    return 0;
}
 
cs

9번 라인을 보면 struct point형 변수를 선언하기 위해 struct point pnt = {2, 3}; 으로 선언한 것을 볼 수 있다. 매번 struct를 사용하는 것은 불편하다. 그렇다면 int num = 10; 처럼 간단하게 할 수 있는 방법이 없을까? 있다. 바로 typedef를 사용하면 된다. 이를 다음의 소스코드와 이전의 소스코드를 비교하며 이해해보자.

<소스코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
 
typedef struct {
    int xpos;
    int ypos;
} Point;
 
int main(void) {
    Point pnt = { 23 };
    printf("[%d, %d]", pnt.xpos, pnt.ypos);
 
    return 0;
}
 
cs

두 코드의 실행결과는 모두 동일하며 3번 라인을 보면 struct 앞에 typedef 키워드가 있고 point라는 구조체 이름이 빠지고 구조체의 마지막에 Point라고 붙여진것을 볼 수 있다. 이를 통해 9번 라인에서 볼 수 있듯이 단순하게 Point pnt = {2, 3}; 으로 구조체 변수를 간단하게 생성하는 것을 볼 수 있다.


함수로의 구조체 변수 전달과 반환

기본 자료형과 마찬가지로 구조체 변수 역시 매개변수에 복사가 되거나 반환될 수 있다. 다음의 코드를 살펴보자.

<소스코드>

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
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
 
typedef struct {
    int xpos;
    int ypos;
} Point;
 
void showPosition(Point pnt) {
    printf("[%d, %d] \n", pnt.xpos, pnt.ypos);
}
 
Point GetCurrentPosition(void) {
    Point cen;
    printf("pos를 입력하시오 : ");
    scanf("%d %d"&cen.xpos, &cen.ypos);
    return cen;
}
 
int main(void) {
    Point curPos = GetCurrentPosition();
    showPosition(curPos);
 
    return 0;
}
 
cs

<실행결과>

라인 9번에서 볼 수 있듯이 매개변수로 구조체 변수가 들어갔고 21번 라인에서 볼 수 있듯 반환값을 Point 변수 curpos에 대입하는것을 볼 수 있다. 또한 매개변수로 구조체의 포인터 변수도 들어갈 수 있다. 이를 통해 Call by reference도 할 수 있다. 이를 예시 코드를 통해 알아보자.

<소스코드>

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
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
 
typedef struct {
    int xpos;
    int ypos;
} Point;
 
void OrgSynTrans(Point* pnt) {
    pnt->xpos = pnt->xpos * -1;
    pnt->ypos = pnt->ypos * -1;
}
 
void showPosition(Point pnt) {
    printf("[%d, %d] \n", pnt.xpos, pnt.ypos);
}
 
int main(void) {
    Point pos = { 7-5 };
    OrgSynTrans(&pos);
    showPosition(pos);
    OrgSynTrans(&pos);
    showPosition(pos);
    return 0;
}
 
cs

<실행결과>

9번 라인에서 볼 수 있듯이 함수의 매개변수로 구조체의 주소값을 넘겨서 원점 대칭하는것을 알 수 있다. 따라서 메인함수의 좌표값을 메인함수 외부에서 변경하는것을 볼 수 있다.

또한 구조체 변수 역시 구조체의 멤버로 포함될 수 있다. 이를 구조체의 중첩이라 한다. 다음의 소스코드를 확인하자.

<소스코드>

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
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
 
typedef struct {
    int xpos;
    int ypos;
} Point;
 
typedef struct {
    Point cen;
    double rad;
} Circle;
 
void showCircleInfo(Circle * c) {
    printf("[%d, %d] \n", (c->cen).xpos, (c->cen).ypos);
    printf("radius: %g \n\n", c->rad);
}
 
int main(void) {
    Circle c1 = { {12}, 3.5 }; // 구조체가 중첩 됨
    showCircleInfo(&c1);
 
    return 0;
}
 
cs

<실행결과>

20번 라인에서 볼 수 있듯이 구조체가 중첩된 것을 알 수 있다.


UNION

구조체와 공용체(union)의 차이

typedef struct {

    int mem1;

    int mem2;

    double mem3;

} Sbox;

 

typedef union {

    int mem1;

    int mem2;

    double mem3;

} Ubox;

위의 Sbox, Ubox의 sizeof 연산을 해보면 각각 16 byte, 8  byte 인것을 확인할 수 있다.  이를 그림으로 표현하면 다음과 같다.

이 union은 하나의 메모리 공간에 둘 이상의 방식으로 접근할 수 있다는 특성이 있다.

 


Enum 열거형

enum을 설명하기 전에 먼저 다음의 소스코드를 보자.

<소스코드>

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
35
36
37
38
39
#include <stdio.h>
 
typedef enum {
    Do = 1, Re = 2, Mi = 3, Fa = 4, Sol = 5, La = 6, Ti = 7
} Syllable;
 
void Sound(Syllable sy) {
    switch (sy) {
    case Do:
        puts("도 입니다.");
        return;
    case Re:
        puts("레 입니다.");
        return;
    case Mi:
        puts("미 입니다.");
        return;
    case Fa:
        puts("파 입니다");
        return;
    case Sol:
        puts("솔 입니다.");
        return;
    case La:
        puts("라 입니다.");
        return;
    case Ti:
        puts("시 입니다.");
        return;
    }
}
 
int main(void) {
    Syllable tone;
    for (tone = Do; tone <= Ti; tone++)
        Sound(tone);
    return 0;
}
 
cs

<실행결과>

열거형은 연관이 있는 이름을 동시에 상수로 선언할 수 있다는 장점이 존재한다.

 

이 포스팅은 윤성우님의 열혈 C를 기반으로 작성되었습니다.

'C' 카테고리의 다른 글

14. 메모리 및 동적할당  (0) 2021.06.28
11. 구조체와 사용자 정의 자료형  (0) 2021.06.27
10. 문자열 관련 함수  (0) 2021.06.27
9. 함수 포인터 & void 포인터  (0) 2021.06.26
8. 2차원 배열과 포인터  (0) 2021.06.26
구조체란?

구조체란 하나 이상의 변수를 묶어서 새로운 자료형을 정의하는 것이다. 예를 들자면 어떤 것의 좌표라고 가정해보자. x좌표 y좌표가 함께 존재해야만 의미가 있고 한 개씩 있으면 존재의 의미가 없는 것이다. 이 구조체를 정의해보자.

 

struct cord {

    int xPos;

    iny yPos;

                                                                      };

위와 같이 정의할 수 있다. 예를 들어 사람의 이름, 나이, 전화번호를 묶으면 다음과 같다

struct person {

    char name[20]; // 배열도 구조체의 멤버가 될 수 있다.

    char phoneNum[20];

    int age;

};

이제 이러한 구조체들을 이용하여 기본 자료형처럼 구조체 변수 선언을 할 수 있다. 다음을 보자.

 

struct type_name val_name;

이를 통해 위의 cord, person 구조체의 변수를 선언하면 다음과 같다. 또한 그림으로 표현하면 다음과 같다.

struct cord pos;

struct person man;

구조체 변수의 멤버에 접근하기 위해서는 다음과 같이 접근한다.

struct_var_name.struct_member_name;

예를 들면 pos.xPos; 이다. 이제 예시를 통해 두 점 사이의 거리를 구하는 예시 코드를 보자.

<소스코드>

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
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>
 
struct point {
    int xpos;
    int ypos;
};
 
int main(void) {
    struct point pos1, pos2; // 구조체 변수 선언
    double dist;
 
    fputs("point1 pos: ", stdout);
    scanf("%d %d"&pos1.xpos, &pos1.ypos); // 구조체 변수 pos1의 멤버 xpos, ypos에 접근 
 
    fputs("point2 pos: ", stdout);
    scanf("%d %d"&pos2.xpos, &pos2.ypos); // 구조체 변수 pos2의 멤버 xpos, ypos에 접근 
 
    // 두 점 사이 거리 계산
    dist = sqrt((double)((pos1.xpos - pos2.xpos) * (pos1.xpos - pos2.xpos) + 
        (pos1.ypos - pos2.ypos) * (pos1.ypos - pos2.ypos)));
 
    printf("두 점의 거리는 %g 입니다. \n", dist);
 
    return 0;
}
 
cs

<실행결과>

다음의 방식으로 구조체 변수를 선언과 동시에 초기화할 수 있다.

<소스코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
 
struct point {
    int xpos;
    int ypos;
};
 
struct person {
    char name[20];
    char phoneNum[20];
    int age;
};
 
int main(void) {
    struct point pos = { 1020 };
    struct person man = { "홍길동""010-0000-0000"21 };
    printf("%d %d \n", pos.xpos, pos.ypos);
    printf("%s %s %d\n", man.name, man.phoneNum, man.age);
    return 0;
}
 
cs

<실행결과>


구조체와 배열 & 포인터

int arr[]; 처럼 int형 배열을 선언할 수 있듯이 구조체 역시 배열을 설정할 수 있다. 만약 struct point가 있다고 가정하면

point형 변수는 struct point pos; 이며 이 point형 배열은 struct point arr[3]; 이다. 이를 그림으로 표현하면 다음과 같다.

소스코드를 통해 알아보자.

<소스코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
 
struct point {
    int xpos;
    int ypos;
};
 
int main(void) {
    struct point arr[3];
    for (int i = 0; i < 3; i++) {
        printf("점의 좌표 입력 : ");
        scanf("%d %d"&arr[i].xpos, &arr[i].ypos);
    }
 
    for (int i = 0; i < 3; i++) {
        printf("[%d, %d] ", arr[i].xpos, arr[i].ypos);
    }
 
    return 0;
}
 
cs

<실행결과>

또한 구조체 배열은 다음과 같이도 초기화가 가능하다.

<소스코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
 
struct person {
    char name[20];
    char phoneNum[20];
    int age;
};
 
int main(void) {
    struct person arr[3= {
        {"김김김""010-1111-1111"21},
        {"이이이""010-2222-2222"25},
        {"박박박""010-3333-3333"28}
    };
 
    for (int i = 0; i < 3; i++)
        printf("%s %s %d \n", arr[i].name, arr[i].phoneNum, arr[i].age);
 
    return 0;
}
 
cs

<실행결과>


구조체 포인터

int num = 5; num의 주소 값을 담는 포인터 변수 int * ptr = &num; 이듯이 구조체의 포인터 변수도 비슷하다.

struct point pos = {11, 22}; struct point * pptr = &pos; 접근 역시 마찬가지로 *ptr = 20; 이듯이 (*pptr).xpos = 10; 이런 식으로 접근할 수 있다. 혹은 다음과 같이 pptr->xpos = 10; 이렇게 접근이 가능하다.

이를 예시로 알아보자.

<소스코드>

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>
 
struct point {
    int xpos;
    int ypos;
};
 
int main(void) {
    struct point pos1 = { 12 };
    struct point pos2 = { 100200 };
    struct point* pptr = &pos1;
 
    pptr->xpos += 4;
    pptr->ypos += 8;
    printf("[%d, %d] \n", pptr->xpos, pptr->ypos);
 
    pptr = &pos2;
    (*pptr).xpos += 5// 이렇게도 접근이 가능하다
    (*pptr).ypos += 10// 이렇게도 접근이 가능하다
    printf("[%d, %d] \n", (*pptr).xpos, (*pptr).ypos);
    return 0;
}
 
cs

<실행결과>


그렇다면 포인터 변수도 구조체 멤버가 당연히 될 수 있다. 이제 예시를 통해 소스코드를 살펴보자.

<소스코드>

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>
 
struct point {
    int xpos;
    int ypos;
};
 
struct circle {
    double radius;
    struct point* center; // 원의 중점 
};
 
int main(void) {
    struct point cen = { 23 };
    double rad = 5.5;
 
    struct circle ring = { rad, &cen };
    printf("원의 반지름: %g \n", ring.radius);
    printf("원의 중심 [%d, %d] \n", (ring.center)->xpos, (ring.center)->ypos);
 
    return 0;
}
 
cs

<실행결과>

이를 그림으로 표시하면 다음과 같다.

 

'C' 카테고리의 다른 글

14. 메모리 및 동적할당  (0) 2021.06.28
12. 구조체와 사용자 정의 자료형 2  (0) 2021.06.27
10. 문자열 관련 함수  (0) 2021.06.27
9. 함수 포인터 & void 포인터  (0) 2021.06.26
8. 2차원 배열과 포인터  (0) 2021.06.26
스트림

스트림에는 입력 스트림, 출력 스트림 2가지가 존재한다. 이 스트림은 운영체제가 제공하는 소프트웨어로 구현되어있는 것으로 파일 스트림 혹은 모니터에 출력하는 출력 스트림 등이 있다.

먼저 문자 단위 입출력 함수를 보자.

  • int putchar(int c);
  • int fputc(int c, File * stream);
  • 함수 호출 성공 시 쓰인 문자 정보 반환, 실패 시 EOF 반환.

여기서 신경 쓸 점은 fputc함수의 경우 파일을 문자를 전송할 스트림을 지정할 수 있다. 즉 파일을 대상으로도 데이터를 전송 가능하다.

다음으로 문자 입력 함수를 보자.

  • int getchar(void);
  • int fgetc(File * stream);
  • 파일의 끝에 도달하거나 함수호출 실패 시 EOF 반환

키보드로부터 하나의 문자를 입력 받는 함수라 할 수 있다. fgetc 함수도 하나의 문자를 입력받지만 fputc함수와 마찬가지로 입력받을 스트림을 지정할 수 있다. 즉 파일을 대상으로 입력이 가능하다.

 

위의 함수들을 보면 함수호출 실패 시 EOF를 반환한다는 것을 알 수 있다. EOF란 End Of File의 약자로써 파일의 끝이라는 의미이다. 파일의 끝은 존재하지만 키보드의 끝은 어떻게 알 수 있을까? 바로 ctrl +  Z이다.(윈도우 기준) 이를 다음의 예시로 알아보자.

<소스코드>

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
 
int main(void) {
    int ch;
    while (1) {
        ch = getchar();
        if (ch == EOF)
            break;
        putchar(ch);
    }
    return 0;
}
cs

<실행결과>

ctrl + Z를 입력하자 종료가 된것을 확인할 수 있다.

이번에는 문자열 입출력 함수에 대해서 알아보자.

  • int puts(const char *s);
  • int fputs(const char* s, FILE * stream);
  • 성공시 음수가 아닌 값, 실패 시 EOF 반환

puts 함수의 경우 호출되면 자동으로 개행이 되지만 fputs함수의 경우 자동으로 개행이 되지 않는다.

  • char * gets(char *s);
  • char * fgets(char *s, int n, FILE * stream);
  • 파일의 끝에 도달하거나 함수호출 실패 시 NULL 포인터 반환

표준 입출력 & 버퍼

버퍼링을 하는 이유는 데이터 전송의 효율성 때문이다. 1층에서 3층까지 책을 옮긴다고 했을때 책을 한권씩 나르는것 보다 가방에 20권씩 담아서 나르는것이 더 효율적이라는 예시를 생각해보면 이를 쉽게 이햐할 수 있다.

출력버퍼를 비우는 fflush함수

int fflush(FILE * stream);

이 함수는 인자로 전달된 스트림의 버퍼를 비우는 기능을 제공한다.

 

그렇다면 입력버퍼를 비워야할 경우가 있을까? 이를 다음의 예시를 통해 알아보자.

<소스코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
 
int main(void) {
    char perID[7];
    char name[10];
 
    fputs("주민번호 앞 6자리 입력: ", stdout);
    fgets(perID, sizeof(perID), stdin);
 
    fputs("이름 입력: ", stdout);
    fgets(name, sizeof(name), stdin);
 
    printf("주민번호: %s \n", perID);
    printf("이름: %s \n", name);
    return 0;
}
cs

<실행결과>

왜 이런 결과가 나타났을까? 우선 주민등록번호를 보면 999999 총 7글자이다.(엔터 포함) 그런데 sizeof(perID)가 7이므로 문자열을 입력받기 위해 널문자를 제외하고 총 6글자를 읽어지는 것이다. 즉 개행 이 입력 버퍼에 남으므로 fgets함수까지 남는다. fgets는 \n을 읽으면 종료하므로 입력버퍼에 남은 \n 만 읽고 종료해버리는 것이다.따라서 위와 같은 경우를 방지하기 위해 입력 버퍼를 비워야 한다. 그러면 어떻게 해야 할까?

바로 함수를 정의하는 것이다. 이를 다음의 예시로 보자.

<소스코드>

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>
 
void ClearLineFromReadBuffer(void) {
    while (getchar() != '\n');
}
 
int main(void) {
    char perID[7];
    char name[10];
 
    fputs("주민번호 앞 6자리 입력: ", stdout);
    fgets(perID, sizeof(perID), stdin);
 
    ClearLineFromReadBuffer();
 
    fputs("이름 입력: ", stdout);
    fgets(name, sizeof(name), stdin);
 
    printf("주민번호: %s \n", perID);
    printf("이름: %s \n", name);
    return 0;
}
cs

<실행결과>

입력 버퍼를 비우니 정상적으로 동작하는것을 알 수 있다.


그 외의 함수들

문자열 길이 반환하는 함수 : strlen

문자열 복사 함수 : strcpy, strncpy

문자열 덧붙이는 함수 : strcat, strncat

문자열 비교 함수 : strcmp, strncmp

문자열의 내용 int로 변환 : atoi

문자열의 내용을 long으로 변환 : atol

문자열의 내용을 double로 변환 : atof

'C' 카테고리의 다른 글

12. 구조체와 사용자 정의 자료형 2  (0) 2021.06.27
11. 구조체와 사용자 정의 자료형  (0) 2021.06.27
9. 함수 포인터 & void 포인터  (0) 2021.06.26
8. 2차원 배열과 포인터  (0) 2021.06.26
7. 이중 포인터  (0) 2021.06.25

함수 역시 함수의 주소 값을 저장할 수 있는 포인터 변수를 선언할 수 있다. 그렇다면 어떻게 선언해야 할까? 먼저 다음의 예시를 살펴보자.

<소스코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
 
int SimpleFunc(int num) {
    return num;
}
 
int main(void) {
    int num;
    scanf("%d"&num);
 
    printf("%d을 입력받았습니다.\n", SimpleFunc(num));
 
    return 0;
cs

위의 SimpleFunc 함수를 보면 다음의 사실들을 알 수 있다. 

함수의 반환형 :  int형

함수의 매개변수 : int형 1개

즉 우리는 함수의 포인터를 선언하기 위해서 함수 포인터의 변수에는 반환형 정보와, 매개변수 선언의 정보를 모두 표시해야 한다. 따라서 함수 포인터 변수는 다음과 같다. int (*fptr) (int) 여기서 fptr은 포인터, 앞의 int는 반환형, 뒤의 int는 매개변수의 형과 그 개수를 의미한다. 이를 예시로 확인해보면 int TwoSimpleFunc(int num1, int num2) 인 경우 위의 규칙을 적용하면 함수의 포인터 변수는 다음과 같다.

int (*fptr) (int, int); 

이 포인터 변수에 TwoSimpleFunc의 주소 값을 저장하는 대입연산은 다음과 같다.

fptr = TwoSimpleFunc;

또한 이 포인터 변수 fptr을 이용해서 fptr(1, 2); 의 형태로도 함수를 호출할 수 있다. -> TwoSimpleFunc(1, 2);와 같은 결과. 예시를 통해 알아보자.

<소스코드>

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
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
 
void TwoAdderFunc(int num1, int num2) {
    int result = 0;
    result = num1 + num2;
    printf("%d 와 %d 의 합은 %d 입니다.\n", num1, num2, result);
}
 
void ShowString(const char* str) {
    printf("%s", str);
}
 
int main(void) {
    int n1, n2;
    scanf("%d %d"&n1, &n2);
 
    const char* str = "함수 포인터";
 
    void (*fptr1)(intint= TwoAdderFunc; // TwoAdderFunc 함수 포인터
    void (*fptr2) (const char*= ShowString; // ShowString 함수 포인터
 
    // 함수 포인터를 통한 호출
    fptr1(n1, n2);
    fptr2(str);
 
    return 0;
cs

<실행결과>

매개변수의 선언으로도 함수 포인터가 올 수 있다. 먼저 이를 예시로 알아보자.

<소스코드>

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
35
36
37
38
#include <stdio.h>
 
int WhoIsFirst(int age1, int age2, int (*fptr) (int n1, int n2)) { // 매개변수로 함수 포인터가 있음
    return fptr(age1, age2); // 함수 포인터로 함수 호출
}
 
int OlderFirst(int age1, int age2) { // 나이가 많은 사람 먼저 하는 함수
    if (age1 > age2)
        return age1;
    else if (age1 < age2)
        return age2;
    else
        return 0;
}
 
int YongerFirst(int age1, int age2) { // 나이가 적은 사람 먼저 하는 함수
    if (age1 < age2)
        return age1;
    else if (age1 > age2)
        return age2;
    else
        return 0;
}
 
int main(void) {
    int age1 = 10, age2 = 20;
    int first;
 
    printf("입장방법 : OlderFirst \n");
    first = WhoIsFirst(age1, age2, OlderFirst); // WhoIsFirst 함수 호출 매개변수로 OlderFirst 함수 넘김
    printf("%d세와 %d세 중 %d세가 먼저 입장! \n\n", age1, age2, first);
 
    printf("입장방법 :  YongerFirst\n");
    first = WhoIsFirst(age1, age2, YongerFirst); // WhoIsFirst 함수 호출 매개변수로 YongerFirst 함수 넘김
    printf("%d세와 %d세 중 %d세가 먼저 입장! \n\n", age1, age2, first);
 
    return 0;
}
cs

<실행결과>


void 포인터

void * ptr; 의 경우 void형 이므로 형이 정해지지 않은 즉 어떤 형태의 주소 값이든 저장할 수 있는 포인터 변수이다.

이를 다음의 예시를 통해 알아보자.

<소스코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
 
void Func(void) {
    printf("그냥 함수");
}
 
int main(void) {
    void* ptr; // void형 포인터
    
    int num = 20;
    ptr = &num; // void형 포인터에 int형 주소 값 저장
    printf("%p \n", ptr);
 
    ptr = Func; // void형 포인터에 Func함수 주소 값 저장
    printf("%p \n", ptr);
 
    return 0;
}
cs

<실행결과>

이렇듯 void 형 포인터의 경우 어떤 형의 주소 값이든 담을 수 있다는 장점이 있지만 연산, 변경, 참조를 할 수 없다는 단점이 존재한다.

(위 포스팅은 윤성우 님의 열혈 C를 인용하였습니다.)

'C' 카테고리의 다른 글

11. 구조체와 사용자 정의 자료형  (0) 2021.06.27
10. 문자열 관련 함수  (0) 2021.06.27
8. 2차원 배열과 포인터  (0) 2021.06.26
7. 이중 포인터  (0) 2021.06.25
6. 다차원 배열  (0) 2021.06.25
2차원 배열

int arr[3][3];

다음과 같은 3행 3열의 2차원 배열이 있다 가정해보자. 이때 각각의 칸들은 칸 안에 쓰여있는 이름이다.

0행0열 0행1열 0행2열
1행0열 1행1열 1행2열
2행0열 2행1열 2행2열

arr[0]은 0행0열 즉 1번째 줄의 1번째, arr[1]은 1행 즉 2번째 줄의 1번째, arr[2]은 2행 즉 3번째 줄의 1번째를 의미한다.
이를 예제로 살펴보며 arr과 arr[0]은 같은 것일까 생각해보자.(출력 결과 자체는 둘다 0행 0열을 가리킨다.)

<소스코드>

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
 
int main(void) {
    int arr[3][3];
    printf("%d \n", arr);
    printf("%d \n", arr[0]);
    printf("%d \n\n"&arr[0][0]);
 
    printf("sizeof(arr) : %d\n"sizeof(arr));
    printf("sizeof(arr) : %d\n"sizeof(arr[0]));
    return 0;
}
cs

<실행결과>

5 6 7 번 라인에서 알 수 있듯이 모두 같은 주소 값을(배열의 0행 0열) 가진다. 그러나 arr은 첫 번째 요소를 가리키며 배열 전체를 가리키지만 arr[0]은 첫번째 요소를 가리키며 배열의 첫 줄 즉 0행만을 의미한다. (3행 3열 즉 9칸이고 int 이므로 4byte. 36byte는 배열의 전체이고 12byte는 배열의 한 줄을 의미한다.)


이번에는 배열 이름에 1을 더한 결과를 알아보자. 앞서서 1차원 배열의 이름에 1을 더한 결과를 알아보았기 때문에 이해할 수 있으리라 생각한다. 예시와 함께 알아보자.

<소스코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
 
int main(void) {
    int arr1[3][2];
    int arr2[3][3];
 
    printf("arr1: %p \n", arr1);
    printf("arr1+1: %p \n", arr1 + 1);
    printf("arr1+2: %p \n", arr1 + 2);
 
    printf("arr2: %p \n", arr2);
    printf("arr2+1: %p \n", arr2 + 1);
 
    return 0;
}
cs

<실행결과>

arr1, arr1+1, arr1+2를 보면 16진수라서 잘 와닿지는 않지만 12씩 차이 나는 것을 볼 수 있고

arr2, arr2+1을 보면 8씩 차이나는것을 볼 수 있다. 이를 그림으로 표현하면 다음과 같다.

즉 한 줄씩 차이 난다는 것을 확인할 수 있는 것이다. 따라서 arr1, arr2는 둘 다 int형 2차원 배열이지만 열의 정보가 다르므로 다른 것을 확인할 수 있다.


2차원 배열의 포인터 형

char (*arr1)[4]; // arr1은 char형 변수를 가리키면서, 포인터 연산 시 1(char의 크기) x 4의 크기 단위로 증가 감소

double (*arr2)[7]; // arr2은 double 변수를 가리키면서, 포인터 연산 시 8(double의 크기) x 8의 크기단위로 증가 감소

 

그렇다면 2차원 배열을 함수의 인자로 전달을 해보자. 예시와 함께 알아보자.

<소스코드>

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
35
36
37
38
#include <stdio.h>
 
void ShowArr(int array[][2], int column) {
    for (int i = 0; i < column; i++) {
        for (int j = 0; j < 2; j++) {
            printf("%d ", array[i][j]);
        }
        printf("\n");
    }
}
 
void ShowArr2(int (*array)[4], int column) {
    for (int i = 0; i < column; i++) {
        for (int j = 0; j < 4; j++) {
            printf("%d ", array[i][j]);
        }
        printf("\n");
    }
}
 
int main(void) {
    int arr1[3][2= {
        {12},
        {34},
        {56}
    };
 
    int arr2[2][4= {
        {5678},
        {9101112}
    };
 
    ShowArr(arr1, sizeof(arr1) / sizeof(arr1[0]));
    puts("");
 
    ShowArr2(arr2, sizeof(arr2) / sizeof(arr2[0]));
    return 0;
}
cs

<실행결과>

3번 라인과 12번 라인에서 볼 수 있듯이 두 가지 방법으로 2차원 배열의 매개변수 선언을 알 수 있다. 이 둘 중 3번 라인의 방식 int array [][3] 이 배열을 매개변수로 전달하는 것을 알기 쉬우므로 이 방법을 권장한다. 또한 두 번째 매개변수로 int column 즉 행의 정보를 넘겨주는 것을 알 수 있는데 이는 2차원 배열의 매개변수가 열 정보만 포함하고 있고 행 정보는 포함하고 있지 않기 때문이다. 따라서 열 정보가 같으면 행의 정보는 달라도 매개변수로 넘겨줄 수 있기 때문에 이를 보완하기 위해서 행 정보를 넘겨주는 것이다.

'C' 카테고리의 다른 글

10. 문자열 관련 함수  (0) 2021.06.27
9. 함수 포인터 & void 포인터  (0) 2021.06.26
7. 이중 포인터  (0) 2021.06.25
6. 다차원 배열  (0) 2021.06.25
5. 포인터와 함수  (0) 2021.06.25

이중 포인터란 포인터 변수를 가리키는 또 다른 포인터 변수를 뜻한다. 이중 포인터 혹은 더블 포인터라고 부르며 

다음과 같이 선언한다. int **dptr; // int형 이중 포인터 

만약 다음과 같은 코드가 있다고 생각해보자.

int main(void) {

    double num = 3.14;

    double * ptr = &num;

    double ** ptr = &ptr;

}

이중포인터가 포인터를 통해 num을 가르키는 모습

<소스코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
 
int main(void) {
    double num = 3.14;
    double* ptr = &num; // 포인터 변수 ptr로 num을 가리킴
    double** dptr = &ptr; // 이중 포인터 변수 dptr로 포인터 변수 ptr을 가리킴
 
    double* ptr2;
 
    printf("%9p %9p \n", ptr, *dptr); // ptr과 *dptr은 같으므로 동일한 출력결과를 보인다.
    printf("%9g %9g \n", num, **dptr); // **dpt과 num은 같으므로 동일한 출력결과를 보인다.
    ptr2 = *dptr; // ptr2 = ptr과 같은 의미
    *ptr2 = 10.99;
    printf("%9g %9g \n", num, **dptr); // num과 **dptr이 같으므로 동일한 출력결과를 보인다.
    return 0;
}
cs

<실행결과>

위의 코드를 그림으로 표현하면 다음과 같다.


포인터 변수의 Call by reference
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
#include <stdio.h>
 
void SwapPtr(int **dptr1, int **dptr2) { // 1번 방법
    int temp;
    temp = **dptr1;
    **dptr1 = **dptr2;
    **dptr2 = temp;
}
 
void SwapPtr2(int** dptr1, int** dptr2) { // 2번 방법
    int* temp;
    temp = *dptr1;
    *dptr1 = *dptr2;
    *dptr2 = temp;
}
 
int main(void) {
    int num1 = 5, num2 = 10;
    int* ptr1 = &num1, * ptr2 = &num2;
    printf("*ptr1 : %d, *ptr2 : %d \n"*ptr1, *ptr2);
 
    SwapPtr(&ptr1, &ptr2);
    printf("*ptr1 : %d, *ptr2 : %d \n"*ptr1, *ptr2);
 
    SwapPtr2(&ptr1, &ptr2);
    printf("*ptr1 : %d, *ptr2 : %d \n"*ptr1, *ptr2);
 
    return 0;
}
cs

<실행결과>


포인터 배열과 포인터 배열의 포인터 형

예제로 알아보자.

<소스코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
 
int main(void) {
    int num1 = 5, num2 = 10, num3 = 15;
    int* ptr1 = &num1, * ptr2 = &num2, * ptr3 = &num3;
 
    int *ptrArray[3= { ptr1, ptr2, ptr3 };
    int** dptr = ptrArray;
 
    printf("%d %d %d \n"*(ptrArray[0]), *(ptrArray[1]), *(ptrArray[2]));
    printf("%d %d %d \n"*(dptr[0]), *(dptr[1]), *(dptr[2]));
    return 0;
}
cs

<실행결과>

7번 라인을 보면 num1, num2, num3의 주소 값이 배열로 저장된 포인터 배열 ptrArray를 확인할 수 있다.

8번 라인을 보면 이중 포인터 dptr에 ptrArray가 대입된다는 것을 알 수 있다. 


'C' 카테고리의 다른 글

9. 함수 포인터 & void 포인터  (0) 2021.06.26
8. 2차원 배열과 포인터  (0) 2021.06.26
6. 다차원 배열  (0) 2021.06.25
5. 포인터와 함수  (0) 2021.06.25
4. 포인터와 배열  (0) 2021.06.22

2차원 배열의 선언은 다음과 같다.

int arr1[3][4];

이는 3행 4열을 가진 이차원 배열 arr1을 의미하며 그림으로 표현하면 다음과 같다.

[0][0] [0][1] [0][2] [0][3]
[1][0] [1][1] [1][2] [1][3]
[2][0] [2][1] [2][2] [2][3]

그렇다면 이 배열의 sizeof 연산을 한 결과를 예시를 통해 확인해보자.

<소스코드>

1
2
3
4
5
6
7
8
9
#include <stdio.h>
 
int main(void) {
    int arr1[3][4];
 
    printf("3행 4열인 배열 arr1의 크기는 %d 입니다.\n"sizeof(arr1));
 
    return 0;
}
cs

<실행결과>

 

위의 표를 보면 3행 4열 이므로 총 12칸이 존재하고 int 형 배열이므로 12 x 4이므로 48바이트 인 것을 확인할 수 있다.

이차원 배열은 다음의 형태로 선언과 동시에 초기화할 수 있는데 총 3가지의 형태가 존재한다. 이를 예시를 통해 확인해보자.

<소스코드>

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
35
36
37
38
#include <stdio.h>
 
int main(void) {
    int arr1[3][3= {
        {123},
        {456},
        {789}
    };
 
    int arr2[3][3= {
        {1},
        {45},
        {789}
    };
 
    int arr3[3][3= { 1234567 };
 
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", arr1[i][j]);
        }
        printf("\n");
    }
    printf("\n");
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", arr2[i][j]);
        }
        printf("\n");
    }
    printf("\n");
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", arr3[i][j]);
        }
        printf("\n");
    }
}
cs

<실행결과>

3차원 배열도 위의 2차원 배열과 비슷하므로 이 포스팅에서는 생략 하겠다.

'C' 카테고리의 다른 글

8. 2차원 배열과 포인터  (0) 2021.06.26
7. 이중 포인터  (0) 2021.06.25
5. 포인터와 함수  (0) 2021.06.25
4. 포인터와 배열  (0) 2021.06.22
3. 포인터  (0) 2021.06.21

+ Recent posts