포인터 완전정복


포인터 완전정복


이글은 이해하기 전에 코드 분석을 할줄 알아야 이해가 된다.

코드 분석도 실력이니 모두 코드 분석을 해야된다.

일단 배우기 전에 알아야 할 것이 있다.

배열 이름은 배열의 시작 주소 라는 것이다..

그게 무슨 말이야?? 한번 코드를 보고 분석을 해보면 된다.


1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int main()
{
    int array[3= { 102030 };
 
    printf("%x %x %x\n", array, array + 0&array[0]);
    printf("%x %x\n", array + 1&array[1]);
    printf("%x %x\n", array + 2&array[2]);
 
    return 0;
}
cs

< 출력 >

53f8e8 53f8e8 53f8e8
53f8ec 53f8ec
53f8f0 53f8f0

배열 이름은 배열의 시작 주소와 똑같다.
잘 모르면은 C언어 배열 주소값과 값 표현방식 여기서 한번 보시고 분석 후 이해하면 된다.
코드 분석도 하나의 실력이니까... 그리고 포인터 변수는 어렵게 생각하지 말고 그냥 주소만 저장 가능한 변수라고 생각하시면 된다.



포인터 변수를 통해서 1차원 배열에 접근을 해보겠다.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
int main()
{
    int array[3= { 102030 };
    int *= array;
 
    printf("%x %x %x\n", p, p + 0&p[0]);
    printf("%x %x\n", p + 1&p[1]);
    printf("%x %x\n", p + 2&p[2]);
 
    return 0;
}
 
cs

< 출력 >

24f790 24f790 24f790
24f794 24f794
24f798 24f798

1차원 포인터 변수 p에 array 배열의 시작 주소를 넣었다. (&array로 해도 된다.)
p+1이라고 되어있는데 p값에 +1을 해주는 것이 아니라 p값의 4바이트 주소를 더해줘라. 이 뜻이다.
각 배열의 주소값을 포인터로 간접접근을 해서 출력해주고 있다.
포인터 변수 p에 배열의 주소를 저장했기에 배열처럼 사용할 수 있다.

다음으로 값에 접근을 해보겠다.

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

< 출력 >

10 10 10
20 20
30 30

값에 접근할때도 배열처럼 접근을 할 수 있다.
이 코드를 봐도 모르겠다면 다시 한번 C언어 배열 주소값과 값 표현방식 여기에 들어가서 보고오는걸 추천한다.


주소의 가감산을 이용해서 배열에 접근을 해보겠다.


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

이런식으로도 접근을 할 수가 있다.
7, 11번째 줄을 p+=1, p+=2로 수정해도 접근이 가능하다.


포인터를 이용해서 배열의 값을 변경할 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
int main()
{
    int array[3= { 102030 };
    int *= array;
 
    *= 40;
    printf("%d %d %d\n", p[0], p[1], p[2]);
 
    *(p + 1= 50;
    printf("%d %d %d\n", p[0], p[1], p[2]);
 
    *(p + 2= 60;
    printf("%d %d %d\n", p[0], p[1], p[2]);
 
    return 0;
}
cs

< 출력 >

40 20 30
40 50 30
40 50 60

이렇게 값이 변경되는것을 볼 수 있다.
p+1은 다시 말하지만 p주소에 4바이트를 더해줘라 이뜻이다.
*(p+1)도 똑같이 설명이 같다. 이쯤되면 포인터가 너무 쉽다고 느껴질 것이다.


위의 코드를 보고 배열을 저렇게 사용 할 수 있으면 문자 배열도 사용할 수 있을거 같지 않나?? 라는 생각이 든다.

한번 해보자.


1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int main()
{
    char array[] = { 'A''B''C' };
    char *= array;
 
    printf("%c %c %c\n", p[0], p[1], [2]);
    printf("%c %c %c\n"*(p + 0), *(p + 1), *(p + 2));
 
    return 0;
}
cs


< 출력 >

A B C
A B C

이렇게 나오는 것을 볼 수 있다. 문자 배열에도 이렇게 접근이 가능하다.


이번에는 배열 포인터를 알아보겠다.

배열 포인터는 2차원 배열을 포인터로 간접접근을 하기위해 쓰이는 방법이라고 보면 된다.

배열 포인터는 int (*p)[3]; 이렇게 선언을 해주면 된다.

자료형 (*이름)[열 길이] 라고 보면 된다.


1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int main()
{
    int array[2][3= { 10,20,30,40,50,60 };
    int(*p)[3= array;
 
    printf("%d %d %d\n", p[0][0], p[0][1], p[0][2]);
    printf("%d %d %d\n", p[1][0], p[1][1], p[1][2]);
 
    return 0;
}
cs

< 출력 >

10 20 30
40 50 60

이렇게 하면 배열 포인터를 2차원 배열처럼 사용이 가능하다.
단, int array[2][5] 라는 배열이 있으면 배열 포인터도 배열 포인터를 선언할때 열 길이에 맞춰서
int (*p)[5]로 바꿔줘야 한다. 그 이유는? 컴파일시 오류가 생기며 실행을 해도 열의 개수가 3인 배열로
컴파일러가 인식을 해서 값이 이상하게 나온다.

1차원적으로도 접근이 가능하다.


1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
int main()
{
    int array[2][3= { 10,20,30,40,50,60 };
    int *= array;
 
    printf("%d %d %d\n", p[0], p[1], p[2]);
    printf("%d %d %d\n", p[3], p[4], p[5]);
 
    return 0;
}
 
cs

이렇게 하면 1차원적으로도 접근이 가능한데 이런 방법은 추천하지 않는다.
warning C4047: '초기화 중'; 'int *'의 간접 참조 수준이 'int (*)[3]'과(와) 다릅니다.
에러코드가 이렇게 뜬다.

포인터 정말 쉽죠잉?

포인터 배열에 대해서 알아보자. 말 그대로 우리가 배열을 선언 하듯이 포인터도 배열이 있다.

자료형 *이름[몇줄?]; 이렇게 변수를 만들어 주면 된다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
int main()
{
    int a = 10, b = 20, c = 30;
    int *pointer[3= { &a, &b, &c };
 
    printf("%x %x %x\n"&a, &b, &c);
    printf("%x %x %x\n", pointer[0], pointer[1], pointer[2]);
    printf("%x %x %x\n"*(pointer + 0), *(pointer + 1), *(pointer + 2));
    printf("\n");
    printf("%d %d %d\n", a, b, c);
    printf("%d %d %d\n"*pointer[0], *pointer[1], *pointer[2]);
    printf("%d %d %d\n"**(pointer + 0), **(pointer + 1), **(pointer + 2));
 
    return 0;
}
cs

< 출력 >

10ffe5c 10ffe50 10ffe44
10ffe5c 10ffe50 10ffe44
10ffe5c 10ffe50 10ffe44

10 20 30
10 20 30
10 20 30

코드를 보면 포인터 배열에 각각 a b c의 주소를 넣어준다.
7~9행까지는 주소를 출력하고 11~13행은 값을 출력한다.
값을 나타낼때에는 *(단항) 연산자를 쓰면 되는데 *는 값을 참조하는 연산자라고 보면 된다.

포인터 배열과 배열 포인터의 차이를 알아보자.

배열 포인터 변수 : int (*p)[3];
배열 포인터 변수 p는 3열짜리 2차원 배열 주소를 저장 할 수 있는 변수이다.

포인터 배열 : int *p[3];
포인터 배열은 주소를 3개 저장 할 수 있는 변수이다.

이 둘의 차이를 잘 모르겠다면 다시한번 글을 보는것을 추천한다.



문자열 배열에 대해서 알아보자.


1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int main()
{
    char arr[3= "ABC";
 
    printf("%c %c %c\n", arr[0], arr[1], arr[2]);
    printf("문자열 크기 출력 : %d\n"sizeof(arr));
 
    return 0;
}
cs

< 출력 >

A B C
문자열 크기 출력 : 4

출력 결과를 보면 arr배열 크기가 4라고 나온다. 왜지???
문자열 맨 뒤에는 "\0"이 자동으로 컴파일러가 넣어준다.
"\0"은 종료문자라고 한다. 문자열이 출력되면 "\0"을 만날때까지 출력을 해준다.

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int main()
{
    char arr[] = "ABC";
 
    printf("%s\n", arr);
    printf("%s\n", arr + 1);
    printf("%s\n", arr + 2);
 
    return 0;
}
cs

%s를 통해서 문자열 출력을 해줄수 있다. 이번에는 종료문자"\0" 존재 유무에 따라 배열의 종류가 달라진다.

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int main()
{
    char arr1[] = { 'A''B''C''D''\0' };
    char arr2[] = { 'A''B''C''D' };
 
    printf("%s\n", arr1);
    printf("%s\n", arr2);
 
    return 0;
}
cs

< 출력 >

ABCD
ABCD꿻꿻꿻꿻ABCD

실행 결과를 보면 arr1 배열은 문자열로 인식을 하고 arr2는 문자로 인식을 하기때문에 8행 출력이 정상적이지가 않다.
"\0" 데이터가 있느냐 없느냐 차이가 이렇게 드러난다.



포인터와 문자열에 대해서 알아보자.


1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
int main()
{
    char *= "ABCD";
    
    printf("%s\n", p);
    printf("%s\n", p + 1);
    printf("%s\n", p + 2);
    printf("%s\n", p + 3);
 
    return 0;
}
cs

< 출력 >

ABCD
BCD
CD
D

이렇게 나온다. 이렇게도 접근이 가능하다. 근데 메모리 상에는 이렇게 저장이 되고있다.

포인터는 항상 주소만 보관하는 변수이다. 그리고 포인터를 통해 문자열을 변경하면 에러가 난다.

왜?? 문자는 상수이기 때문이다. 만약 문자를 변경하고 싶으면 문자열을 배열에 저장해서 변경하면 된다.


포인터 정말 쉽죠잉??



포인터 변수 상수화에 대해서 알아보자.
포인터는 여러가지 주소를 담을수가 있다. 근데 프로그램을 작성할때 이 포인터 변수는 값이 변경 안되게끔 해야된다.
그러면 상수화를 시켜야 되는데 상수화 시키는 명령어는 const다. 말 보다는 코드로 표현하는게 더 쉬울것 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
int main()
{
    char a = 'A';
    char b = 'B';
    char *const p = &a;
 
    *= 'C';
 
    printf("%c\n"*p);
    printf("%c\n", a);
 
    p = &b;
 
    return 0;
}
cs

여기 13행에서 오류가 난다. 그 이유는 포인터 변수 p는 const로 지정해놨기 때문이다.
오로지 p에는 a 주소만 들어가 있어야 한다. 변경은 절대로 안된다.

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int main()
{
    char a = 'A';
    char b = 'B';
    const char *const p = &a;
 
    *= 'C';
 
    return 0;
}
cs

여기 8행에서 오류가 난다. 그 이유는 const char *const p = &a;로 해놨기 때문이다.
값 변경도 안되고 주소 변경도 안된다. 난 이 방법을 이렇게 표현한다.
자료형 뒤에 const가 있으면 값 변경 불가. 이름 뒤에 const가 있으면 주소 변경 불가.
이렇게 정리를 한다.



void형 포인터에 대해서 알아보겠다. void는 ~이 하나도 없다 라는 뜻이다.

void형 포인터는 자료형이 없는 포인터 변수라고 보면 된다. int형이든 char형이든 아무거나 다 들어간다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
 
int main()
{
    char c = 10;
    double d = 3.1;
 
    void *= &c;
    printf("e의 주소값 : %x\n", e);
    printf("c의 주소값 : %x\n"&c);
    e = &d;
    printf("e의 주소값 : %x\n", e);
    printf("d의 주소값 : %x\n"&d);
 
    return 0;
}
cs

< 출력 >

e의 주소값 : 4ff983
c의 주소값 : 4ff983
e의 주소값 : 4ff970
d의 주소값 : 4ff970

이렇게 나오는것을 볼 수 있다. 어떤 자료형이든 아무거나 다 넣을수 있다.
단, 포인터 변수로 값을 표현할때는 이렇게 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
 
int main()
{
    char c = 10;
    double d = 3.1;
 
    void *= &c;
    printf("e의 주소값 : %x\n", e);
    printf("c의 값 : %d\n"*(char*)e);
    e = &d;
    printf("e의 주소값 : %x\n", e);
    printf("d의 값 : %lf\n"*(double*)e);
 
    return 0;
}
cs

< 출력 >

e의 주소값 : 137f7af
c의 값 : 10
e의 주소값 : 137f79c
d의 값 : 3.100000

이렇게 void형 포인터를 쓸때는 강제 형 변환을 위에와 같이 해줘야 한다.
값을 넣을때도 *(자료형*)e = 5.1; 이렇게 해줘야 한다.



이번에는 함수 포인터에 대해서 알아보자.

함수 포인터 선언은 자료형(*이름)(매개변수) 이렇게 선언을 해주면 된다.

그냥 코드로 보자.


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>
void hello()
{
    printf("코드를 분석해라의 블로그\n");
}
int add(int i)
{
    i = i + 1;
    return i;
}
int main()
{
    int a = 10;
    int(*fp)() = hello;
    int(*fp1)(int= add;
    fp();
 
    fp = add(a);
    a = fp1(a);
    printf("%d\n", a);
 
    return 0;
}
cs

< 출력 >

코드를 분석해라의 블로그
11

14번째 줄에 hello함수의 주소를 넣는다. 함수의 이름은 함수의 주소이다.
그리고 15번째 줄에 매개변수가 있는 add 함수 주소를 넣는다.
사용법은 이러하다.




이제 구조체 포인터에 대해서 알아보자.

① 구조체 멤버 변수 포인터


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 point
{
    int *a;
    int *b;
};
int main()
{
    int num1 = 4;
    int num2 = 5;
    struct point p1;
 
    p1.a = &num1;
    p1.b = &num2;
 
    printf("%x %x\n", p1.a, p1.b);
    printf("%x %x\n"&num1, &num2);
 
    p1.a = NULL//구조체 이름과 구조체 첫번째 멤버 주소값 같음.
    printf("%x %x\n"&p1, &p1.a);
    return 0;
}
cs


< 출력 >

5ffb28 5ffb1c
5ffb28 5ffb1c
5ffb0c 5ffb0c

20번째 줄에 구조체 이름과 구조체 첫번째 멤버 주고가 같다는것을 보여주고 있다.
배열 이름이 배열 시작주소라는 것과 같다는 개념이다.

뭐.. 나머지는 설명은 안해도 알거라고 밑는다 모르겠다면 위에부터 다시 읽는것을 추천한다.

② 1차원 포인터와 2차원 포인터 같이 사용한 경우

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
struct point
{
    int *a;
    int **b;
};
int main()
{
    int num1 = 10;
    struct point p1;
 
    p1.a = &num1;
    p1.b = &p1.a;
 
    printf("%d %d %d\n", num1, *p1.a, **p1.b);
    return 0;
}
cs

< 출력 >
10 10 10

이것도 설명 안해도 알겠다. 그냥 활용법만 보여주고 있는 것이다.

③ 구조체 변수로 포인터 사용하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
struct point
{
    char name[20];
    char url[40];
};
int main()
{
    struct point po = { "aossuper7""aossuper8.tistory.com" };
    struct point *= &po;
 
    printf("%s %s\n", po.name, po.url);
    printf("%s %s\n", (*p).name, (*p).url);
    printf("%s %s\n", p->name, p->url);
 
    return 0;
}
cs

< 출력 >

aossuper7 aossuper8.tistory.com
aossuper7 aossuper8.tistory.com
aossuper7 aossuper8.tistory.com

구조체 포인터 변수를 만들었다. 포인터를 이용해서 구조체에 접근하는 방법은 13, 14행에 있다.
(*p).name , (*p).url 방법과 p->name, p->url 방법이 있다.
(*p).name을 설명하자면 먼저 p에 접근을해서 name 가져와라 이렇게 생각하면 이해가 될 것이다.
활용법은 이러하고 접근을 할때 . 연산자보다 -> 연산자를 더 많이 활용한다. -> 연산자에 익숙해지자.

④ 구조체 변수 2차원 포인터 사용하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
struct point
{
    char name[20];
    char url[40];
};
int main()
{
    struct point po = { "aossuper7""aossuper8.tistory.com" };
    struct point *= &po;
    struct point **pp = &p;
 
    printf("%s %s\n", po.name, po.url);
    printf("%s %s\n", (*p).name, (*p).url);
    printf("%s %s\n", p->name, p->url);
    printf("%s %s\n", (**pp).name, (**pp).url);
    printf("%s %s\n", (*pp)->name, (*pp)->url);
 
    return 0;
}
cs

출력 안해도 어떤 값이 출력되는지 알것이다.

2차원 구조체 포인터 접근 방법은 이러하고 (*pp)->name을 설명하자면 (*pp)->name == (*p).name == po.name 과 같다.


⑤ 자기 참조 구조체와 외부 참조 구조체


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
{
    char name[20];
    int money;
    struct point *link;
};
int main()
{
    struct point p1 = { "aossuper7"90NULL };
    struct point p2 = { "aossuper8"80NULL };
    struct point p3 = { "aossuper9"70NULL };
 
    p1.link = &p2;
    p2.link = &p3;
 
    printf("%s %d\n", p1.name, p1.money);
    printf("%s %d\n", p1.link->name, p1.link->money);
    //p1.link 안에 p2의 name, money 출력
    printf("%s %d\n", p1.link->link->name, p1.link->link->money);
    //p1.link 안에 p2.link 안에 p3의 name, money 출력
    return 0;
}
cs

< 출력 >
aossuper7 90
aossuper8 80
aossuper9 70

이 코드는 구조체 안에 자기를 참조하는 구조체 포인터 변수를 포함하고 있다.
설명은 주석으로 달아 놨다. 그림으로 설명하자면



이런식으로 되어있다. 포인터를 배울때는 항상 그림으로 생각을 하면 편하다.


⑤ - 1 외부 참조 구조체


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>
struct point
{
    int a;
    int b;
};
struct blog
{
    char name[20];
    struct point *link;
};
int main()
{
    struct blog b1 = { "aossuper7"NULL };
    struct blog b2 = { "aossuper8"NULL };
    struct point p1 = { 3040 };
    struct point p2 = { 1020 };
 
    b1.link = &p1;
    b2.link = &p2;
 
    printf("%s %d %d\n", b1.name, b1.link->a, b1.link->b);
    printf("%s %d %d\n", b2.name, b2.link->a, b2.link->b);
 
    return 0;
}
cs


< 출력 >
aossuper7 30 40
aossuper8 10 20

blog 구조체는 현재 point의 구조체를 외부참조 하고있다.
이것도 그림으로 표현해보자.

이렇게 되어있다. 이정도면 이해가 됬을거다.


포인터 완전정복 끝!!!!

포인터 너무 쉽다. 끝!!


댓글

Designed by JB FACTORY