CSE/C Language 검색 결과

45개 발견
  1. 미리보기
    2015.09.16 - Palpit

    [C Language] 이클립스 C/C++ 개발 환경 구축 !!

  2. 미리보기
    2015.08.10 - Palpit

    [C Language] 43. 포인터 - 매개변수 처리 총정리

  3. 미리보기
    2015.08.05 - Palpit

    [C Language] 42. void형 포인터 - C 언어

  4. 미리보기
    2015.08.05 - Palpit

    [C Language] 41. 포인터의 개념을 깨는 '0'

  5. 미리보기
    2015.08.05 - Palpit

    [C Language] 40. 문자열 함수 - C 언어

  6. 미리보기
    2015.08.05 - Palpit

    [C Language] 39. 포인터의 포인터 - C 언어

  7. 미리보기
    2015.08.04 - Palpit

    [C Language] 38. 문자열과 포인터 - C 언어

  8. 미리보기
    2015.08.04 - Palpit

    [C Language] 37. 2차원 배열과 포인터 - C 언어

조회수 확인

윈도우에서 이클립스 IDE를 통해서 C / C++ 개발 환경을 구축해 보도록 하겠습니다!




이클립스를 통해 개발을 하는 이유는...


솔직히 Visual Studio 설치하기엔 너무 무겁고, 오래걸리고, 걍 깔끔해보이지만, 그래도 이클립스가 더 손에 익어서..?





아무튼.





1. 이클립스를 다운 받아서 압축해제 합니다.



http://www.eclipse.org/


에 접속하셔서 다운로드를 누르신 후, 아래와 같은 'Eclipse IDE for C/C++ Developers' 버전으로 다운받아주세요 !!



 * Ubuntu 이든 64 or 32 bit 이든 맞게 받으시길 바랍니다 !




압축해제하시면 아래와 같이 구성되어 있을 겁니다 !










2. 아래 사이트 진입 후, Installer를 다운받습니다 !!


http://www.mingw.org/



 다운받은 인스톨러를 실행하면 초기화면은 아래와 같습니다 !!


 여기서 install 버튼 클릭 !










3. 아래 화면에서 걍 바로 'continue' 버튼 클릭 !!







설치가 진행되는 것이 보이실 겁니다!



















4. 다 진행이 되면 Continue 버튼 클릭하면 아래처럼 화면이 구성집니다.


 아래 체크된 3 개의 구성요소들을 각각 체크해주시면 됩니다. Mark for Installation


 










 각각 체크하신 후, Installation -> Apply Change 를 클릭 !! 


 그다음 Apply 버튼 클릭 !!











5. 설치가 완료되면 MinGW 종료 후, eclipse를 실행시킵니다 !


 * workspace는 다들 알아서 설정하시길...




 그 전에 환경 변수 설정을 통해 path에 mingw 를 등록합니다. 초기 설치 디렉터리는 C 드라이브 이므로 아마 저와 같은 경로일 것입니다.










 실행시키신 후, Window -> Preferences 에 진입합니다.










 다음 C/C++ -> New C/C++ Project Wizard -> Makefile Project 로 진입하여서 Builder Settings 탭을 아래와 같이 수정합니다.








설정 후, restart 해줍니다.













 File -> New  -> C 혹은 C++ Project를 선택합니다!



 아래 화면에서 Project name은 알아서 설정하시고, Toolchains에 MinGW GCC를 선택합니다 !! 그리고 Finish !

 











6. 프로젝트명 폴더에 오른쪽 클릭하여 New -> Source 해서 C 프로그램이면 *.c 확장명으로 파일을 생성합니다 !!


 소스 파일 안에 간단한 C 프로그램을 작성합니다 !!













7. Ctrl + B 혹은 망치를 눌러 Build 한 뒤 Ctrl + F11 을 통해 실행 결과 확인 !!









이상없이 작동합니다!!







다른 카테고리의 글 목록

CSE/C Language 카테고리의 포스트를 톺아봅니다
조회수 확인

1. 매개변수가 1차원 배열인 경우

 1차원 배열이 int 형인 경우


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
/*
 * parameter.c
 *
 *  Created on: 2015. 8. 10.
 *      Author: Yeonsu
 */
 
#include <stdio.h>
 
void print_temp(int *int);
 
int main(void) {
 
    int sum;
    int tmp[] = { 1142198257 };
 
    print_temp(tmp, sizeof(tmp) / sizeof(int));
 
    return 0;
}
 
void print_temp(int *tmp, int length) {
    int i;
 
    for (i = 0; i < length; i++)
        printf("tmp[%d] = %d\n", i, *tmp++);
}
 
 
cs











 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
 
/*
 * parameter.c
 *
 *  Created on: 2015. 8. 10.
 *      Author: Yeonsu
 */
 
#include <stdio.h>
 
void print_name(char *);
 
int main(void) {
 
    char name[] = "Yeonsu Moon";
 
    print_name(name);
 
    return 0;
}
 
void print_name(char *my_name) {
    for (; *my_name;)
        putchar(*my_name++);
}
 
cs















2. 매개변수가 2차원 배열인 경우

 2차원 배열이 int 형인 경우



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
40
41
42
43
44
45
46
 
/*
 * parameter.c
 *
 *  Created on: 2015. 8. 10.
 *      Author: Yeonsu
 */
 
#include <stdio.h>
 
void print_element(int (*)[3]);
 
int main(void) {
 
    int tmp[][3= { { 613 }, { 524 } };
 
    print_element(tmp);
 
    return 0;
}
 
void print_element(int (*tmp)[3]) {
 
    int i, j;
 
    puts("첫 번째 방법");
 
    for (i = 0; i < 2; i++)
        for (j = 0; j < 3; j++)
            printf("1. [%d][%d] = %d\n", i, j, tmp[i][j]);
 
    puts("");
    puts("두 번째 방법");
 
    for (i = 0; i < 2; i++)
        for (j = 0; j < 3; j++)
            printf("2. [%d][%d] = %d\n", i, j, *(tmp[i] + j));
 
    puts("");
    puts("세 번째 방법");
    for (i = 0; i < 2; i++)
        for (j = 0; j < 3; j++)
            printf("3. [%d][%d] = %d\n", i, j, *(*(tmp + i) + j));
 
}
 
cs











 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
39
40
41
 
/*
 * parameter.c
 *
 *  Created on: 2015. 8. 10.
 *      Author: Yeonsu
 */
 
#include <stdio.h>
#define ROW 7
#define COLUMN 10
 
void print_week(char (*)[10], int);
 
int main(void) {
 
    char days[ROW][COLUMN] = { "Sunday""Monday""Tuesday""Wednesday",
            "Thursday""Friday""Saturday" };
 
    print_week(days, ROW);
 
    return 0;
}
 
void print_week(char (*days)[10], int row) {
 
    int i, j;
 
    for (i = 0; i < row; i++)
        puts(days[i]);
 
    puts("");
 
    for (i = 0; i < row; i++) {
        for (j = 0; j < COLUMN; j++)
            putchar(days[i][j]);
        puts("");
    }
 
}
 
cs











 문자열을 다룰 때 2차원 배열을 사용하는 경우는 거의 없다(메모리 낭비가 너무 심하기 때문이다). 


 위 프로그램은 print_week()의 본체에서 세번 째 for문은 문자열 출력을 마친 상태에서도 공회전한다는 것을 알 수 있다(문자열의 길이가 6이면 6번 회전해야 하는데 무조건 10번 회전한다). 


 이 문제는 위 구문을 아래와 같이 수정하여서 공회전을 막을 수 있다.


1
2
3
for (j = 0; days[i][j]; j++)
    putchar(days[i][j]);
 
cs











 days[i][j]가 NULL 문자이면 문자열의 끝이므로 더 이상 회전하지 않는다.




 

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
 
/*
 * parameter.c
 *
 *  Created on: 2015. 8. 10.
 *      Author: Yeonsu
 */
 
#include <stdio.h>
 
void print_week(char **);    // 함수 선언
 
int main(void) {
 
    char *days[] = { "Sunday""Monday""Tuesday""Wednesday""Thursday",
            "Friday""Saturday", NULL };
 
    print_week(days);        // 함수 호출
 
    return 0;
}
 
void print_week(char **days) {        // 함수 본체 정의
 
    int i;
 
    for (i = 0; i < 7; i++)
        puts(days[i]);
 
    puts("");
    for (; *days; days++) {
        for (; **days; (*days)++)
            putchar(**days);
        puts("");
    }
 
}
 
cs



 






 매크로를 사용하면 복잡하게 보이기 때문에 이번 예제는 매크로 변수를 삭제했다. 

 문자열을 포인터 배열에 저장하고 이를 활용한 것인데, 함수 선언에 * 연산자가 2개 필요하다는 것과 본체에서 받아들일 때도 2개 필요하다는 것을 알 수 있다. 

 그리고 puts()를 사용할 때는 2차원 배열이 문자인 경우와 같지만 문자 하나하나를 출력하는 것은 문자배열보다 복잡해 보일 것이다. 

 하지만 사실 이것은 하나의 기교일 뿐 2차원 배열이 문자일 경우와 같다. 그러므로 다음처럼 사용해도 무방하다.



1
2
3
4
5
6
7
for (i = 0; i < 7; i++
{
    for (j = 0; days[i][j]; j++)
        putchar(days[i][j]);
    puts("");
}
 
cs






 


다른 카테고리의 글 목록

CSE/C Language 카테고리의 포스트를 톺아봅니다
조회수 확인

1. void 형 포인터란?

 널 포인터가 포인터임에도 불구하고 어느 곳도 가리키고 있지 않는 데 반해, void 형 포인터는 무엇이든 가리킬 수 있는 포인터이다.

 널 포인터가 아무것도 넣을 수 없는 자루라면 void 형 포인터는 무엇이든 들어가는 자루이다.


  void 형 포인터는 현재 가리키고 있는 대상체가 정해져 있지 않는 포인터이다.


 바꾸어 말하면 현재 가리키는 대상체가 정해져 있지 않기 때문에 대상체만 정해지면 얼마든지 사용할 수 있다는 의미이다.







2. void 형 포인터 변수 정의

 char 형 포인터는 'char *' 로 표기할 수 있고 int 형 포인터는 'int *'로 표기할 수 있다. 마찬가지로 void 형 포인터는 'void *'로 표기한다.



  void   *void_p; 



 void_p는 포인터 변수이기 때문에 당연히 메모리에 4 바이트가 할당된다. 또한, 포인터 변수이기 때문에 주소 값 이외에는 할당될 수 없다. 그렇다면 void_p가 가리키는 값은 어떤 형일까? 

 지금까지는 포인터 변수를 정의할 때 포인터 변수 앞에 형을 지정함으로써 형을 명시했다. 

 void 형 포인터 변수는 정의할 때 아무것도 가리키고 있지 않다는 의미로 'void *'로 지정했기 때문에 현재는 무엇을 가르키고 있는지 알 수 없다. void 형 포인터 변수를 사용할 때 포인터 변수가 가리킬 형이 프로그램을 진행하면서 정해지는 이유가 이것 때문이다.






3. void 형 포인터의 성질

 void 형 포인터는 다음과 같은 성질이 있다.


 - 어떠한 형 변환(cast 연산자) 없이도 void 형 포인터 변수에 주소 값을 할당할 수 있다.

 - void 형 포인터 변수에서 값을 읽을 때는 반드시 캐스트 연산자를 사용해야 한다.

 - '*' 연산자(간접 지정 연산자)를 사용할 때는 항상 캐스트 연산자를 사용해야 한다.

 - void 형 포인터 변수에 ++, --를 사용할 때는 항상 캐스트 연산자를 사용해야 한다.


 위의 void 형 포인터 변수 성질들은 원리만 알면 너무도 당연한 내용이므로 굳이 외우려고 할 필요가 없다. 


 - 어떠한 형 변환(cast 연산자) 없이도 void 형 포인터 변수에 주소 값을 할당할 수 있다.


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
 
/*
 * voidp.c
 *
 *  Created on: 2015. 8. 5.
 *      Author: Yeonsu
 */
 
#include <stdio.h>
 
int main(void) {
 
    int num = 123;
    float num_f = 56.6;
    char text_c = 'M';
 
    void *void_p;
 
    void_p = &num;
    void_p = &num_f;
    void_p = &text_c;
 
    return 0;
}
 
cs




 컴파일을 해보면 에러가 발생하지 않는다. 위의 예제로 확인할 수 있는 것은 void 형 포인터 변수에 주소 값을 대입할 때는 어떠한 형 변환도 필요없다는 것이다.

 



 - void 형 포인터 변수에서 값을 읽을 때는 반드시 캐스트 연산자를 사용해야 한다.

 






 여러분이 다른 사람의 집을 방문했다고 가정해 보자. 닭고기가 너무 먹고 싶어서 냉장고 문을 열었다. 100% 닭고기를 냉장고에서 꺼낼 수 있는 경우는 여러분이 닭고기를 넣은경우와 다른 사람이 닭고기 넣는 것을 본 경우이다. 

 void 형 포인터 변수도 마찬가지다. void 형 포인터가 냉장고라 할 때 그곳에서 int 형을 꺼내려고 한다면 그곳에 여러분이 int형을 넣든지 넣은 것을 본 경우이다. 프로그램은 다른 사람이 대신 작성해 주는 것이 아니므로 전자가 이에 해당된다. 결론을 말하면 void 형 포인터 변수에서 무언가를 꺼내려고 한다면 그 안에 무엇이 들어 있는지 미리 알아야 한다는 것이다. void 형 포인터 변수에 무엇이 들어 있는지 모르는 상태에서 무조건 꺼내려고 한다면 원하지 않는 결과를 얻을 것이다.



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
 
/*
 * voidp.c
 *
 *  Created on: 2015. 8. 5.
 *      Author: Yeonsu
 */
 
#include <stdio.h>
 
int main(void) {
 
    int num = 123;
    float num_f = 56.6;
    char text_c = 'M';
 
    void *void_p;
 
    void_p = &num;
    printf("int %d\n"*((int *) void_p));
 
    void_p = &num_f;
    printf("float %f\n"*((float *) void_p));
 
    void_p = &text_c;
    printf("char %c\n"*((char *) void_p));
 
    return 0;
}
 
cs










 - '*' 연산자(간접 지정 연산자)를 사용할 때는 항상 캐스트 연산자를 사용해야 한다.

 포인터 변수에 '*' 연산자를 사용한다는 의미는 포인터 변수에 할당된 주소를 참조하여 값을 구하는 것이다. 값을 구하려면 그 값이 int 형인지 double 형인지 알아야 주소 값에서 4, 8 바이트를 읽어 올 수 있다. 그러므로 void 형 포인터에서 '*' 연산자를 사용할 때는 항상 캐스트 연산자가 필요하다. 바로 전 예제를 보면 int 형 123을 얻기 위해 '*'를 사용했고 '*'를 사용하기 위해서 캐스트 연산자가 사용되었다. 캐스트 연산자를 사용하지 않으면 주소가 가리키는 곳에서부터 몇 바이트를 읽어 올지 알 수 없으므로 void 형 포인터에서 값을 취할 때는 캐스트 연산자가 반드시 필요하다.





 - void 형 포인터 변수에 ++, --를 사용할 때는 항상 캐스트 연산자를 사용해야 한다.

 ++ 이나 -- 의 의미는 x = x + 1 이나 x = x - 1과 같은 수식을 줄여서 사용한 것이다. x = x + 1을 사용하려면 x 에서 값을 구하고 여기서 1 을 더한 후 다시 x 에 넣어야 한다. x 에서 값을 구하려면 형이 정해져야 하고 형이 정해지기 위해서는 캐스트 연산자의 사용이 필수적인 것이다.



 


다른 카테고리의 글 목록

CSE/C Language 카테고리의 포스트를 톺아봅니다
조회수 확인

지금까지 배운 포인터에 대한 개념으로 설명이 불가능한 프로그램이 있다. 아래 예제를 보도록하자.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
 * nullp.c
 *
 *  Created on: 2015. 8. 5.
 *      Author: Yeonsu
 */
 
#include <stdio.h>
 
int main(void) {
 
    char *char_p;
 
    char_p = 0;
 
    printf("%x\n", char_p);
 
    return 0;
}
 
cs








포인터를 설명할 때 수없이 강조했던 내용이 '포인터 변수에는 주소 값 이외에는 어떠한 값도 할당할 수 없다'라는 것이다. 하지만 위의 프로그램으로 0 이라는 정수 값에 포인터 변수에 할당되고 있으며 컴파일 문제가 발생하지 않는다. 컴파일러는 위의 프로그램을 어떻게 해석하길래 문제가 발생하지 않는 것일까?


컴파일러는 0을 포인터 변수에 할당하는 순간, 이를 자동으로 널 포인터로 인식하여 처리한다. 즉, 0이 포인터화되어 포인터 변수에 할당되기 때문에 문제가 발생하지 않는 것이다. 이때 다른 정수는 해당하지 않는다. 오로지 정수 0만 해당된다. 널 포인터는 다음과 같은 의미를 가진다.


 널 포인터는 어떠한 곳도 가리키고 있지 않는 주소 값이다.




1. 널 포인터의 사용 용도

 아무것도 가리키지 않는 포인터를 도대체 어디에 써먹는가? 의아해 하겠지만 널 포인터는 이미 여러분이 알게 모르게 많이 사용하고 있는 개념이다.

 

 - 에러 처리

 - 매개변수의 마지막을 알릴 때


 에러 처리

  

 

1
2
if ((fp = fopen("temp""r")) == NULL)
    exit(0);
cs



  fopen()을 사용하여 'temp' 파일을 개방하려고 했는데, 현재 디렉터리에 'temp' 파일이 존재하지 않아서 fopen() 함수가 널 포인터를 리턴한 것이다.







1
2
3
if ((temp = (char *)malloc(1024)) == NULL)
    exit(0);
 
cs


  1024 bytes의 메모리를 확보하려 했지만 실패했을 경우에 널 포인터가 리턴된다.



 매개변수의 마지막을 알릴 때


  매개변수의 마지막을 위한 널 포인터 예제이다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 
/*
 * nullp.c
 *
 *  Created on: 2015. 8. 5.
 *      Author: Yeonsu
 */
 
#include <stdio.h>
 
int main(int argc, char **argv, char **env) {
 
    for(;*env;env++)
        puts(*env);
 
 
    return 0;
}
 
cs









 





다른 카테고리의 글 목록

CSE/C Language 카테고리의 포스트를 톺아봅니다
조회수 확인

1. gets(), fgets()

 문자열을 사용자로부터 받아들일 때 가장 많이 사용하는 함수는 바로 gets()이다. 하지만 이것은 상당히 위험한 함수이며 초보자는 반드시 fgets()를 사용해야 한다. 이번 단원에서는 gets()의 위험성을 알아보고 이 대신 fgets()를 사용할 것을 권장한다.


 gets()는 리눅스 메뉴얼에 보면 사용하지 말라는 뜻에서 '저주받은 함수'라는 극한적인 말로 표현되어 있다. 얼마나 위험한지 아래 예제를 통해 알아보도록 하자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 
/*
 * gets.c
 *
 *  Created on: 2015. 8. 5.
 *      Author: Yeonsu
 */
 
#include <stdio.h>
 
int main(void) {
 
    char name[5];
 
    gets(name);
    printf("Your name is %s\n", name);
 
    return 0;
}
 
cs










 전혀 위험해 보이지 않지만 위의 프로그램은 다음과 같은 메모리 구조를 가짐으로 써 프로그램을 묵시적인 위험에 빠트리고 있다.







 gets()를 잊기 위해서는 대체 함수가 필요한데 이것이 바로 fgets() 이다. gets()는 get string의 의미가 있으며 fgets()에서의 'f'는 file을 뜻한다. 그런데 C에서의 file이라는 것은 화면, 테이프, CD 등을 포괄하기 때문에 화면을 통한 문자열 입력도 fgets()로 가능하다.




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 
/*
 * gets.c
 *
 *  Created on: 2015. 8. 5.
 *      Author: Yeonsu
 */
 
#include <stdio.h>
 
int main(void) {
 
    char name[5];
 
    fgets(name, sizeof(name), stdin);
 
    printf("Your name is %s\n", name);
 
    return 0;
}
 
cs











 

 안전하게 확보된 영역은 5 바이트이며 널 문자를 포함해야 하므로 4 문자 'Moon'이 name에 저장되고 출력되었다. 






2. strcpy()

 


1
2
3
4
5
6
7
8
char *imsip;
char dim[20];
 
strcpy(imsip, "Yeonsu Moon");
strcpy(dim, "Yeonsu Moon");
 
imsip = "Yeonsu Moon";
dim = "Yeonsu Moon";
cs



 위 코드를 보면 5번과 7번 라인은 문제가 없는 라인이고, 4번과 8번 라인은 문제가 있는 라인이다.


 4번 라인의 문제를 보기 전에 strcpy()의 원형을 보도록 하자.




char *strcpy(char *s1, const char *s2); 


 첫 번째 인자와 두 번째 인자 모두 주소 값을 원하고 있고 리턴되는 값도 주소 값이라는 것을 알 수 있다.

 문제가 된 4번의 첫 번째 인자 imsip는 포인터 변수이기 때문에 분명히 주소 값을 가지고 있을 것이다. 또한, 두 번째 인자인 "Yeonsu Moon" 은 문자열이기 때문에 'Y'가 저장된 주소 값이 인자로 전달될 것이다. strcpy()의 원형을 해석하면 s2가 가리키는 주소 값에서 값을 취하여 s1이 가리키고 있는 주소 값에 저장하는 것이다.

 여기까지 해석해도 큰 문제가 없어 보인다. 하지만 사실 한 가지 위험요소를 놓치고 있다. strcpy()의 원형을 해석한 것을 토대로 위 코드를 그림을 통해 살펴보자.









 strcpy(imsip, "Yeonsu Moon");







 strcpy(imsip, "Yeonsu Moon")을 사용하면 첫 번째 인자의 주소와 두 번째 인자의 주소를 취한 후 두 번째 인자가 가리키는 주소에서 문자를 하나씩 imsip가 가리키고 있는 주소쪽으로 복사하기 시작한다. 조금 쉽게 설명하면 'Yeonsu Moon' 이라는 문자열을 imsip가 가리키고 있는 곳에 복사해 넣는다는 뜻이다. 그런데 문제는 imsip에 있다. imsip가 가리키고 있는 주소는 어디에서도 초기화를 해준적이 없기 때문에 쓰레기 주소 값(0x24FF)이 존재한다.쓰레기 주소 값이라는 의미는 메모리의 어디를 가리킬지 아무도 알 수 없다는 말이다. 그림에서는 0x24FF 였지만 내일은 무슨 주소 값이 저장될지 아무도 모른다. 그 아무도 알 수 없는 영역에 'Yeonsu Moon'이라는 문자열을 쓰려고 하기 때문에 프로그램은 더 이상 진행하지 않고 메모리 에러를 출력하는 것이다. 반대로 imsip가 안전한 영역을 가리키고 있다면 strcpy(imsip, "Yeonsu Moon")은 잘 동작할 것이다.

 적용을 시키기 이전에 초기화를 시켜주면 안정적으로 동작할 것이다.




 strcpy(dim, "Yeonsu Moon");






 앞의 그림으로 안전한 영역 20 바이트에 "Yeonsu Moon"이라는 문자열이 하나씩 복사된다는 것을 알 수 있다. 그러므로 문제를 발생시키지 않는다. 




다른 카테고리의 글 목록

CSE/C Language 카테고리의 포스트를 톺아봅니다
조회수 확인

1. 포인터의 포인터란?

 이해를 돕기 위해서 배열을 먼저 예로 들어 보자.

  

  - 1차원 배열: int apple[5];

  - 2차원 배열: int banana[2][10];

  - 3차원 배열: int chocolate[3][4][5];


 1차원 배열은 관리 대상이 1차원이며 모두 5개의 요소로 구성되어 있다.

 2차원 배열은 관리대상이 2차원이다. 2차원 이라는 것은 1차원 배열의 모임이므로 이것을 '배열의 배열'이라고 칭할 수 있다. 배열의 배열이라는 것은 결국 배열을 관리하는 배열이 존재한다는 것이다.

 3차원 배열은 '배열의 배열의 배열'이 되며 2차원 배열을 관리하는 배열이라고 생각하자. 


 이를 포인터로 적용하면 아래와 같다.


  - 포인터 변수 정의: char *apple;

  - 포인터의 포인터 변수 정의: char **banana;

  - 포인터의 포인터의 포인터 변수 정의: char ***chocolate;


 포인터 변수 정의는 문자열 중에서 가장 첫 번째 문자의 주소를 가리킨다. 포인터 변수를 사용하면 하나의 문자열을 다룰 수 있다.

 포인터의 포인터 변수 정의는 배열과 같은 맥락으로 해석하면 포인터를 관리하는 포인터 변수가 된다. 즉, 포인터가 여러 개 존재하고 이를 관리하는 하나의 포인터가 존재하는 것이다. 





2. 포인터의 포인터 변수를 왜 사용하는가?

 포인터의 포인터 변수를 사용하는 주된 목적은 다음과 같다.

 

  1. 매개변수가 포인터 배열인 경우 (문자열 집합)

  2. 명령행 인자를 사용하는 경우 (문자열 집합)


 쉽게 말하면 하나의 함수에서 사용하던 문자열들의 집합이나 2 차원 배열 등을 다른 함수에 넘겨 처리하기 위해 포인터의 포인터 개념이 필요한 것이다.

 명령행 인자도 이의 연장선상에서 이해하면 무리가 없다. 만약 포인터의 포인터라는 개념이 없다면 문자열을 하나씩 다른 함수에 넘겨서 처리해야 하는 번거로움이 발생한다. 개념상 어려운 것은 없지만 확실히 이해해 두지 않으면 사용방법상 큰 문제가 된다.

 포인터의 포인터에서 파생될 수 있는 표현식은 아래와 같은 것이 있다.


 **char_p, *char_p, &*char_p, &char)p, ...


 별표 단항 연산자가 하나 더 들어감으로써 표현식이 조금 복잡해 보인다. 이것 때문에 많은 사람들이 포인터의 포인터를 어려워한다.

 별표 하나만 붙여야 하는지 2개를 붙여야 하는지, 넘길 때 어떻게 표현해야 하는지 등 생각보다 초보자에게 넘어야 할 산이 많다.






3. 포인터의 포인터 변수 정의 방법과 초기화

 포인터의 포인터 변수 정의 방법


1
2
3
4
5
6
int **imsipp_int;
 
char **imsipp_char;
 
float **imsipp_float;
 
cs


 정의 방법은 포인터 변수 앞에 '*' 만 하나 더 추가하면 된다. 이것으로써 포인터를 관리하는 포인터 변수가 생성된 것이다.

 포인터의 포인터 변수는 그 자체만으로는 아무 의미가 없다. 방금 언급한 것처럼 포인터의 포인터 변수는 포인터를 관리하는 포인터이다. 즉, 관리 대상이 존재할 때만 포인터의 포인터가 의미가 있는 것이다. 



 포인터의 포인터 변수 초기화


1
2
3
4
5
6
7
8
char zero;
char *one;
char **two;
 
zero = 'u';
one = &zero;
two = &one;
 
cs









 zero는 1 바이트를 할당받고 이곳에 u를 저장한다. one은 포인터 변수로 zero를 가르키고 있으며 zero의 주소를 저장한다.

 two는 포인터의 포인터 변수로 one을 가리키고 있고 one이 저장되어 있는 주소를 저장한다. 주의할 점은 one 안에 저장된 주소 값(zero의 주소 값)이 아니라 one이 저장된 위치를 저장한다는 것이다. 위 그림으로 파악할 수 있는 사실은 다음과 같다.


 1. 포인터의 포인터 변수는 일종의 포인터 변수이다. 그러므로 주소 값만 저장할 수 있다.

 2. 포인터의 포인터 변수가 관리하는 대상은 포인터 변수이다. 관리하는 대상이 포인터 변수이기 때문에 주소 값이라고 무조건 넣을 수는 없다는 것이다. 가령 two에 zero의 주소 값을 넣으려고 하면 문법 에러가 발생한다. two에 zero의 주소 값을 항당하면 two가 관리하는 대상이 u라는 아스키 값이 되므로 문법적으로 맞지 않게 된다. 포인터 변수는 어느 주소 값이든 모두 받아들이지만 포인터의 포인터 변수는 아무 주소 값이나 다 넣을 수 없다.




1
2
3
4
5
6
7
8
9
char *one[3];
char **two;
 
one[0= "palpit";
one[1= "Everytown";
one[2= "Town";
 
two = one;
 
cs









 이전의 예제는 two = &one 이라는 형식을 사용했고, 바로 위 예제는 two = one 이라고 '&' 없이 바로 할당했다.

 one은 포인터 배열이기 때문에 &가 없어도 된다. 이를 먼저 이해하고 다음 예제를 분석해 보기로 하자. 위의 간이 예제를 수행하면 포인터의 포인터 변수는 다음과 같은 메모리 구조를 가진다.


1
2
char *one[3];
char **two; 
cs







 4개의 변수는 초기화 되지 않았기 때문에 모두 물음표로 표기했다. 포인터의 포인터 변수도 일종의 포인터 변수이기 때문에 4 바이트를 할당받는다는 것에 주목하자.



 

1
2
3
4
one[0= "palpit";
one[1= "Everytown";
one[2= "Town";
 
cs







 문자열 3개가 메모리의 어딘가(0x2000, 0x2007, 0x2010)에 저장되고 이 주소를 one[0], one[1], one[2]가 나누어서 저장하고 있다.



  

1
2
two = one;
 
cs





 마지막으로 two는 문자열을 직접 가리키는 것이 아니라 문자열들을 가리키고 있는 포인터 배열을 가리킨다. two 는 one[0], one[1], one[2] 중에서 가장 첫 번째 요소의 가장 첫 번째 주소 값을 가리키므로 0x1000을 저장한다. 0x2000을 저장하지 않는다는 것을 눈여겨 보도록 하자.


 이전의 예제에서 one은 zero가 저장된 위치 하나만을 가리키고 있었다. 하지만 위 예제에서는 one은 문자열 3개를 관리하고 있는 포인터 배열이다. 그러므로 one을 two에 할당할 때 &가 필요 없는데 만약 문자열 하나만을 포인터의 포인터 변수가 가리키도록 하고 싶다면 다음처럼 사용해야 한다.


1
2
3
4
two = &one[0];    
two = &one[1];
two = &one[2];
 
cs



 출력은 아래와 같이 할 수 있다.


1
2
3
4
two = &one[0];
putchar(**two);        // print 'p'
puts(*two);            // print 'palpit'
 
cs



 위와 같이 사용하면 하나의 문자열만을 다루게 되는데, 포인터의 포인터 변수에 대한 특성을 고려하지 않은 것이므로 추천하지 않는다.







4. 포인터의 포인터 참조

 가장 간단한 포인터의 포인터 참조 예제를 살펴보자. 포인터 배열을 포인터의 포인터 변수가 할당받아 처리한 예제이다.



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
 
/*
 * pointe.c
 *
 *  Created on: 2015. 8. 5.
 *      Author: Yeonsu
 */
 
#include <stdio.h>
 
int main(void) {
 
    int i;
    char *tmp[3];
    char **imsipp;
 
    tmp[0= "palpit";
    tmp[1= "every";
    tmp[2= "town";
 
    imsipp = tmp;
 
    for (i = 0; i < 3; i++)
        puts(tmp[i]);
 
    for (i = 0; i < 3; i++)
        puts(*imsipp++);
 
    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
 
/*
 * pointe.c
 *
 *  Created on: 2015. 8. 5.
 *      Author: Yeonsu
 */
 
#include <stdio.h>
 
void join(char **);
 
int main(void) {
 
    int i;
    char *tmp[3];
 
    tmp[0= "palpit";
    tmp[1= "every";
    tmp[2= "town";
 
    join(tmp);
 
    return 0;
}
 
void join(char **imsipp) {
    int i;
 
    for (i = 0; i < 3; i++)
        printf("%s ", imsipp[i]);
 
    puts("");
    for (i = 0; i < 3; i++)
        printf("%s "*imsipp++);
}
 
cs







다른 카테고리의 글 목록

CSE/C Language 카테고리의 포스트를 톺아봅니다
조회수 확인

1. 문자열이란?

 "In C, strings are arrays of characters."


 C에서의 문자열에 대한 정의를 보면, 문자열 형이 없고 단지 문자들의 집합인 배열로 처리한다는 것을 알 수 있다. 또한, 포인터적인 관점에서 본다면 시스템은 문자열 전체에 전혀 관심이 없다. 시스템은 오로지 문자열 중에서도 가장 첫 번째 문자가 저장된 곳의 위치에만 관심이 있을 뿐이다. 문자열의 끝은 널 문자로 판별하기 때문에 널 문자가 없는 문자열은 C에서 상당히 위험한 존재로 여겨지고 메모리 에러의 대부분을 차지한다.





2. 문자열 포인터 변수

 int 형 포인터 변수가 int 형 변수를 가리킬 수 있는 것과 마찬가지로 문자열 포인터 변수도 문자열을 가리킬 수 있다. 좀 더 정확히 말하면 문자열 중에서도 가장 첫 번째 문자가 저장된 곳을 주소를 기억한다.


 문자열을 만나는 순간 시스템은 문자열을 메모리 어딘가에 저장하고 그 주소를 리턴한다. 이때의 주소는 문자들 중에서 가장 첫 번째 문자가 저장된 곳의 주소이다. 


 아래 예제로 문자열 단위 출력을 해보도록 하자.



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
 
/*
 * str.c
 *
 *  Created on: 2015. 8. 4.
 *      Author: Yeonsu
 */
 
#include <stdio.h>
 
int main(void) {
 
    char *char_p = "I am a good boy";
 
    for (; *char_p; char_p++) {
        if (*char_p != ' ')
            putchar(*char_p);
        if (*char_p == ' ')
            putchar('\n');
    }
    puts("");
 
    return 0;
}
 
cs











3. 포인터 배열

 여기서는 문자열을 저장할 때 배열을 사용했을 때와 포인터를 사용했을 때의 차이점을 명확히 설명함으로써 포인터가 얼마나 메모리 공간을 효율적으로 사용하는 지 검증해 보기로 한다.


 배열을 사용하여 문자열을 저장한 경우


1
2
3
4
5
6
7
8
9
10
char name[6][20];
 
strcpy(name[0], "Kim Dong Guk");
strcpy(name[1], "Lee Dong Guk");
strcpy(name[2], "Park Dong Guk");
strcpy(name[3], "Jung Dong Guk");
strcpy(name[4], "Han Dong Guk");
strcpy(name[5], "Moon Dong Guk");
 
 
cs


 여섯 명의 이름을 저장하기 위해 모두 120바이트를 할당하였다. 한 사람의 이름을 저장하기 위한 공간이 들쑥날쑥하기 때문에 넉넉하게 20바이트를 준비하였다. 그 덕분에 남는 공백은 모두 낭비되고 있다. 배열을 사용하여 이름을 저장하는 방식은 한눈에 보아도 메모리 낭비가 심하다는 것을 알 수 있다.



 포인터 배열을 사용하여 문자열을 저장한 경우


1
2
3
4
5
6
7
8
9
char *name[6];
 
name[0= "Kim Dong Guk";
name[1= "Lee Dong Guk";
name[2= "Park Dong Guk";
name[3= "Jung Dong Guk";
name[4= "Han Dong Guk"
name[5= "Moon Dong Guk";
 
cs




 name[0]이라는 포인터 변수는 'Kim Dong Guk'이라는 문자열에서 K가 저장된 주소를 기록한다. name[1]은 'L'의 주소를 기록하고 각각 맨 앞의 문자열의 주소를 저장하는데 이와 같이 처리하면 배열을 사용할 때와는 달리 메모리 낭비가 전혀 없음을 알 수 있다.










4. a, 'a', "a"

 a와 'a' 그리고 "a"는 C에서 전혀 다른 특성을 가지고 있다. 


1
2
3
4
5
6
7
char a;         // 1 byte
short b;         // 2 bytes
int c;            // 4 bytes
 
'a'                // 1 byte
"a"                // 2 bytes, a \0
 
cs



 마지막 "a"는 문자열 상수로 C에서의 모든 문자열은 널 문자로 끝나기 때문에 눈에 보이지 않는 널 문자가 자동적으로 추가된다.







5. 포인터 변수와 배열명의 차이

 - 할당받는 메모리의 크기가 다르다.

  배열명: 배열며 자체로는 메모리를 차지하지 않고 배열첨자와 형(type)에 따라서 크기가 결정된다.

  포인터 변수: 포인터를 저장하는 변수이기 때문에 무조건 4 바이트를 할당받는다.


 - 문자열을 저장하는 형식이 다르다(복사와 할당의 차이가 발생)

  배열명: 문자열 상수가 메모리에 저장되는 것이 아니라 문자 하나 하나가 배열에 복사된다.

  포인터 변수: 문자열 상수가 메모리에 저장되고 이 위치가 포인터 변수에 할당된다.

 

 - 증감연산자으 사용 여부가 다르다.

  배열명: 증감 연산자를 사용할 수 없다.

  포인터 변수: 증감 연산자를 사용할 수 있다.

다른 카테고리의 글 목록

CSE/C Language 카테고리의 포스트를 톺아봅니다
조회수 확인

1. 대상체의 개념

 다음은 2행 3열짜리 tmp 배열을 정의한 것이다.


  

1
2
int tmp[2][3];
 
cs



 int 형을 저장할 수 있는 방이 6(2X3)개가 생성되었다. 2 차원 배열을 정의하고 사용하는 것은 쉬워 보이나 대상체의 개념이 적용되면서 2 차원 배열은 생각보다 쉽게 정복되지 않는다.



 아래 예제를 통해 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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/*
 * pointer.c
 *
 *  Created on: 2015. 7. 21.
 *      Author: Yeonsu
 */
 
#include <stdio.h>
 
#define PRINT_D(x) printf(#x" = %d\n", x)
#define PRINT_P(x) printf(#x" = %#X\n", x)
 
int main(void) {
 
    int tmp[2][3= { { 7915 }, { 217911 } };
 
    puts("<== (tmp) ==>");
    PRINT_P(tmp);
    PRINT_P(tmp + 1);
    PRINT_D(sizeof(tmp));
    PRINT_D(sizeof(tmp + 1));
    PRINT_P(*tmp);
    PRINT_P(&tmp);
    PRINT_D(sizeof(*tmp));
    PRINT_D(sizeof(&tmp));
 
    puts("");
    puts("<== (tmp[0]) ==>");
    PRINT_P(tmp[0]);
    PRINT_P(&tmp[0]);
    PRINT_P(tmp[0+ 1);
    PRINT_D(sizeof(tmp[0]));
    PRINT_D(sizeof(tmp[0+ 1));
    PRINT_D(*tmp[0]);
    PRINT_P(&tmp[0]);
    PRINT_D(sizeof(*tmp[0]));
    PRINT_D(sizeof(&tmp[0]));
 
    puts("");
    puts("<== (tmp[0][0]) ==>");
    PRINT_D(tmp[0][0]);
    PRINT_D(tmp[0][0+ 1);
    PRINT_P(&tmp[0][0]);
    PRINT_P(&tmp[0][0+ 1);
    PRINT_D(sizeof(tmp[0][0]));
    PRINT_D(sizeof(tmp[0][0+ 1));
    PRINT_D(sizeof(&tmp[0][0]));
 
 
    return 0;
}
 
 
cs









 Line 18: tmp가 저장된 곳의 주소를 출력한다. 그런데 tmp는 메모리에 저장된 값이 아니다. tmp는 배열명이므로 주소 값 자체이고 메모리 할당을 받지 않는다.


 Line 19: tmp + 0은 첫 번째 자식의 주소 값을 의미하므로 0x28FF18가 출력되고, tmp + 1은 두 번째 자식의 주소 값이므로 0x28FF24가 출력된다.


 tmp 에게는 tmp[0]와 tmp[1]이라는 자식이 있다. 이를 다른 말로 표현하면 'tmp의 관리대상은 tmp[0]와 tmp[1]이다'라고 말할 수 있다. 첫 번째 관리대상인 tmp[0]의 주소는 tmp + 0 이며 두 번째 자식의 주소는 tmp + 1로 표현된다.


 Line 20: tmp가 관리하는 대상은 tmp[0]과 tmp[1]이라는 자식이다. 그런데  그 자식 하나는 12바이트로 구성되어 있으므로 24가 출력된다.


 Line 21: tmp + 1은 대상체의 개념이 전혀 없는 주소 값이므로 크기는 4가 출력된다. 대상체의 개념이 있는 모든 변수에 정수를 더하면 그것은 대상체의 개념이 사라진 단순한 주소 값이라는 것을 기억해 두자.


 Line 22: 어떠한 포인터 변수에 *를 사용하면 포인터 변수가 가리키는 대상체가 된다. 그것이 모(母) 배열이라면 대상첸느 하위 배열인 부분배열이 되고 가장 하위의 부분배열일 경우 특정한 값을 뜻한다. 여기서 tmp는 모배열이므로 *tmp를 사용하면 tmp의 부분배열 중에서 첫 번째 부분배열을 지칭하므로 위와 같은 주소 값이 출력된다.


 Line 23: 조금 전에 확인해 본 것처럼 tmp는 메모리에 할당된 변수가 아니기 때문에 tmp와 &tmp의 값은 같다.


 Line 24: *tmp는 tmp 가 관리하는 두 자식 중에서 첫 번째 자식을 말한다. 여기서는 tmp[0]이므로 tmp[0]의 크기를 출력할 것이다. tmp[0]은 tmp[0][0], tmp[0][1], tmp[0][2]를 자식으로 가지기 때문에 12가 출력된다. 


 Line 29: tmp[0]이 0x28ff18이라는 주소 값을 가질 때 &tmp[0]의 주소 값은 무엇일까? tmp[0][0], tmp[0][1],... 등은 메모리를 할당받지만 tmp, tmp[0], tmp[1] 등은 포인터 자체를 나타낼 뿐 메모리를 할당 받는 것이 아니다. tmp[0]과 &tmp[0]의 주소 값은 같다.


 Line 31: 일단 tmp[0]이라는 주소 값에 정수를 더했으므로 결과는 주소 값이란 것을 알 수 있다. 0x28ff1c라는 주소는 무엇을 가리키고 있을까? tmp[0]은 tmp[0][0], tmp[0][1], tmp[0][2] 이라는 자식들을 가지고 있다. 이 중에서 '+1' 만큼 떨어진 자식을 뜻하므로 여기서는 tmp[0][1]이 된다. 


 Line 34: tmp[0]은 세 명의 자식을 거느린 부모이다. *tmp[0]은 이 중에서 첫 번째 자식을 지칭한다. 두 번째 자식은 *(tmp[0] + 1)로 표현할 수 있다. 결국 첫 번째 자식은 *(tmp[0] + 0)과 같다는 것을 알 수 있다. 문법적으로 말하면 *(tmp[0] + 0), *(tmp[0] + 1), *(tmp[0] + 2)는 tmp[0]이 가리키고 있는 곳에서 0번째, 1번째, 2번째 떨어진 요소이다라고 말할 수 있다. tmp 라는 조상은 tmp[0], tmp[1]이라는 부모들을 관리대상으로 삼았기 때문에 tmp에 정수를 더하면 부모 사이를 움직이고, tmp[0]이나 tmp[1]은 각각 세 명의 자식들을 관리하기 때문에 tmp[0]이나 tmp[1]에 정수를 더하면 자식 사이를 움직이는 것이다.


 Line 36: '*는 []와 완전히 일치한다'라는 개념을 떠올려보자. 그러면 앞의 연산은 tmp[0][0]이 된다. *tmp[0]은 정수 값을 뜻하며 int 형이기 때문에 4가 출력된다.



 위를 정리하면 아래와 같다.



 

 int tmp[2][3]; 일때


  

  1. tmp는 tmp[0], tmp[1]을 대상으로 하며 그 하위의 존재를 알지 못한다.

  2. tmp[0]은 tmp[0][0], tmp[0][1], tmp[0][2]를 대상으로 한다.

  3. tmp[1]은 tmp[1][0], tmp[1][1], tmp[1][2]를 대상으로 한다. 

  4. tmp, tmp[0], tmp[1]은 메모리 할당을 받지 않기 때문에 

    tmp == &tmp

    tmp[0] == &tmp[0]

    tmp[1] == &tmp[1]

     의 관계가 성립한다.

  5. *tmp == *(tmp + 0) == tmp[0]이 성립한다.

  6. *(tmp + 1) == tmp[1]이 성립한다. 







2. 2차원 배열 포인터 정의와 초기화

 1차원 배열이 있고 이를 포인터에 접목하기 위해서 1차원 포인터 변수를 사용했다. 포인터 변수는 1차원 배열을 가리키기 때문에 포인터 변수를 사용하여 1차원 배열의 모든 것을 다룰 수가 있었다. 마찬가지로 2차원 배열을 다루기 위해서는 2차원 배열 포인터가 마련되어 있다. 2차원 배열 포인터를 정의하는 방법을 1차원과 비교해서 살펴보자.



 1차원 배열 포인터 정의와 할당


 

1
2
3
4
int  tmp[10= {12345678910};
int *tmp_p;
 
tmp_p = tmp;
cs


 

  tmp라는 배열명을 가볍게 tmp_p에 할당해 주면 tmp_p에 tmp의 주소 값이 할당되고 tmp_p를 이용하여 tmp를 다루게 된다. 

  tmp_p는 주소 값을 저장할 수 있는 포인터 변수이며 tmp[0]이 저장된 주소 값을 가리키게 된다.




 2차원 배열 포인터 정의와 할당


1
2
3
4
5
int tmp[2][3= {{123}, {456}};
int (*tmp_p)[3];
 
tmp_p = tmp;
 
cs



  tmp 는 2차원 배열이다. 2차원 배열을 2차원 포인터 변수에 할당하는 것은 1차원과 차이가 없다. 단지 변수를 정의할 때만 차이가 나난다. 초보자는 (*tmp_p)[3]이라는 정의에 심한 거부감을 느낄 것이다. 일반적인 프로그램에서 거의 사용되지 않으므로 초보자들에게는 다행이라 하겠다.


 일단 정의된 표현을 하나씩 살펴보자. 가장 눈에 거슬리는 것은 괄호인데, 이것을 제거하면 *tmp_p[3]이 된다. (*tmp_p)[3]과 *tmp_p[3]은 의미에서 하늘과 땅만큼의 차이가 나기 때문에 다음 단원에서 다룰 것이다. 


 (*tmp_p)[3]이라고 정의하면 이것은 한행이 3열로 구성된 2차원 배열을 다루겠다는 의미이다. 즉, 가르키는 대상이 2행이든 5행이든 100행이든 한 행이 3열로 구성되어 잇다면 할당에는 문제가 발생하지 않는다는 말이다. 그러므로 (*tmp_p)[3]에서의 3은 행이 아니라 열에 대한 첨자가 된다.



 아래 예제를 통해 2차원 포인터 변수를 사용한 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
 
/*
 * poit.c
 *
 *  Created on: 2015. 8. 4.
 *      Author: Yeonsu
 */
 
#include <stdio.h>
 
int main(void) {
 
    int i, j;
    int tmp[3][2= { { 12 }, { 34 }, { 56 } };
    int (*tmp_p)[2= tmp;
 
    puts("use dimension");
    for (i = 0; i < 3; i++)
        for (j = 0; j < 2; j++)
            printf("tmp[%d][%d] = %d\n", i, j, tmp[i][j]);
 
    puts("");
    puts("use pointer");
    for (i = 0; i < 3; i++)
        for (j = 0; j < 2; j++)
            printf("tmp[%d][%d] = %d\n", i, j, *(*(tmp_p + i) + j));
 
    return 0;
}
 
cs









3. 왜 대상체가 중요한가?

 대상체는 우리가 값을 꺼내 오려는 대상을 일컫는 말이다. 그 대상은 특정한 값일 수도 있지만 주소 값일 수도 있다. 꺼내 오려는 대상을 정확히 알지 못하면 원하는 결과 값이 나오지 않았을 때 그 이유를 정확히 파악하지 못하고, 포인터 변수에 *, 괄호 등을 수 없이 이리저리 이동하면서 결과 값을 얻어 내려고 진땀을 뺀다. 우연히 재수가 좋아서 몇 번 *를 여기저기 붙여 보다가 원하는 결과 값을 얻었다고 하더라도 그 건 우연일 뿐 실력이 아니기 때문에 나중에 똑같은 경우를 만나면 마찬가지로 많은 시간을 소비해야 결과 값을 얻을 수 있다는 안타까운 결론이 나온다.


 대상체를 모르는 순간 원하는 값을 얻을 수 없다.


 

 대상체를 모르면 정말 원하는 값을 얻을 수 없는지 아래 예제를 통해 살펴보자.


1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
 
int main(void) {
 
    int tmp[2][3= {{654}, {321}};
    int (*tmp_p)[3];
 
    tmp_p = tmp;
 
    ...
}
 
cs

 



 1. 포인터 변수(tmp_p)를 사용하여 tmp 배열에 있는 모든 값을 출력하라.

 2. 포인터 변수(tmp_p)를 사용하여 각 행의 합을 구하라.

 3. 포인터 변수(tmp_p)를 사용하여 4와 2를 출력하라.



단, 포인터 변수를 사용할 때 배열 방식의 표현이 아닌 별표(*) 단항 연산자를 사용해야 한다.



 tmp_p는 2차원 배열을 가리키고 있다. tmp_p로 tmp의 모든 요소를 다룰 수 있게 된 것이다. 하지만 tmp_p, &tmp_p, tmp_p[0], *tmp_p, ... 등을 모르면 tmp의 모든 요소를 자유자재로 다루기란 사실상 어렵다. 특히 대상체를 모르면 그때 그때의 상황에 따라서 주먹구구식으로 프로그램을 진행하는데 그야말로 고역이 아닐 수 없다.


 예를 들어 보자.


 tmp_p = tmp를 사용해서 2차원 배열인 tmp를 tmp_p가 가리키도록 했다. 첫 번째 배열 요소인 6을 얻기 위해서는 tmp_p는 어떻게 표현해야 할까?


 1. tmp_p

 2. *tmp_p

 3. **tmp_p







 

1번 tmp_p는 배열전체를 대상체로 하기 때문에 답이 아니며 *tmp_p는 하나의 행을 대상체로 하기 때문에 역시 답이 될 수 없다.

**tmp_p는 tmp[0][0], tmp_p[0][0]과 같은 표현이므로 하나의 배열요소를 가리키고 있고 6을 뜻한다. 정확히 표현하면 tmp_p가 가르키고 있는 곳에서 0번째 행 0번째 열의 위치로 이동한 후 그곳의 값을 가리키는 것이다. 0번째 행 0번째 열의 요소는 6이므로 3번이 정답이다.



그렇다면 두 번째 배열요소는 **tmp_p + 1 이라고 표현해야 할까? **tmp_p는 6이라는 값이며 6 + 1은 7이 되므로 두 번째 배열의 개념과는 거리가 멀다. tmp_p + 1 은 행 단위로 동작하기 때문에 답이 될수 없으며, 행에서 배열요소 단위로 움직이는 것이 있어야 두 번째 배열요소를 구할 수 있다. *tmp_p + 1 가 이러한 표현에 해당된다. 하지만 *tmp_p는 첫 번째 행을 뜻하며 주소 값이므로 '+1'을 하면 역시 주소 값이 된다. 그러므로 *가 하나 더 있어야 배열의 요소를 구할 수 있다. *tmp_p + 1이 주소 값이므로 *(*tmp_p + 1)은 그 주소 값을 가리키는 영역의 값이므로 5가 된다. '주소 값 + 주소 값' 역시 주소 값이라는 것을 기억하고 있다면 어렵지 않게 이해할 수 있을 것이다. 


대상체의 개념이 중요하게 쓰이는 것 중의 하나가 함수의 인자로 넘겨질 때인데, 나중에 이에 대해 자세히 배울 것이다. 여기서는 맛보기로만 살펴보자.



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
 
/*
 * poit.c
 *
 *  Created on: 2015. 8. 4.
 *      Author: Yeonsu
 */
 
#include <stdio.h>
 
int add(int *int);
 
int main(void) {
 
    int result;
 
    int tmp[2][3= {{123}, {456}};
 
    result = add(tmp[0], 3);
    printf("첫 번째 행의 합계: %d\n", result);
 
    result = add(tmp[1], 3);
    printf("두 번째 행의 합계: %d\n", result);
 
 
    return 0;
}
 
int add(int *data, int length) {
    int i;
    int sum = 0;
 
    for (i = 0; i < length; i++)
        sum += *data++;
 
    return sum;
}
 
cs











다른 카테고리의 글 목록

CSE/C Language 카테고리의 포스트를 톺아봅니다