네이버 지식인에 다음 코드에 대한 질문이 있었다. 오류 원인이 뭐냐고.

#include <stdio.h>

void main()
{
int arr[2][3]={{1,2,3},{4,5,6}};
int *p;
int i;

p=arr;

for(i=0; i<sizeof(arr)/sizeof(int);i++)
{
    printf("%d \n",*(p+i));
}
}


 간단해 보여서 codepad에서 돌려보니 잘되었다. 그렇다면 질문자는 왜 안된다고 했을까. VS에서는 돌려보니 안된다.


 어찌저찌 코드는 아래와 같이 수정하여 돌아가게 하였다. 그런데 arr와 arr[0]의 주소 값은 같은데 왜 p=arr;는 안되는지 약간 의문이 들었다. codepad에서도 정상적인 결과가 나왔기 때문에 더 궁금했다.


 지인들의 답변을 듣고 깨우침을 얻었다. 역시 나는 별로 아는게 없다. ㅠㅠ


 그래서 최종적인 답변으로 아래와 같이 작성했다. 덕분에 하나 배워간다.


#include <stdio.h>

void main()
{
int arr[2][3]={{1,2,3},{4,5,6}};
//int arr2[6]={1,2,3,4,5,6};
int *p;
int i;

p=arr[0];
//printf("%d %d %d\n",arr,arr[1],arr[1][2]);
//printf("%d %d %d\n",arr,*(arr+1),*(*(arr+1)+2));


for(i=0; i<sizeof(arr)/sizeof(int);i++)
{
    printf("%d \n",*(p+i));
    //printf("%d \n",*(*(arr+1)+2));
}


주석된 부분은 배열과 포인터 관련 참고 하시라고 남겨 놓았습니다.

int arr[6];이라는 배열이 있을 때 p=arr라고 하면 arr자체가 arr의 시작점, 즉 배열의 첫번째 값인 arr[0]의 주소를 뜻합니다.
따라서 p=&arr[0] 이것도 가능합니다.

이차원배열 int arr[2][3];에서는 구조상 차이를 잘 살펴보셔야 합니다.
arr[0][0]정수(integer)형이고 arr[0]정수형 배열입니다. arr정수형 배열의 배열입니다. 따라서 정수형 포인터인 p에는 정수형 배열인 arr[0]이 대입됩니다.
p=&arr[0][0] 이것도 가능합니다. 첫번째 값의 주소이기 때문입니다.

'프로그래밍 > C' 카테고리의 다른 글

포인터에 관한 고찰  (0) 2014.10.30
assert 매크로 사용법  (0) 2014.01.27
malloc & free 기본 사용법  (0) 2013.07.23
typedef  (0) 2011.01.22
공용체와 열거체  (0) 2011.01.19
구조체  (0) 2010.10.23
배열과 포인터의 관계  (0) 2010.10.09
1.포인터 연산과 배열과의 관계
 int *p;가 선언되어 있고 p에 주소 0x1000전지가 저장되어 있다고 가정하면 p+1은 어떤 값을 가질까? 0x1001이 될 것이라고
생각할 수도 있는데 여기서 +1은 int형 하나의 크기를 의미한다. 즉, int형은 4바이트이므로 주소 값에 +4가 되어 0x1004가 된다.
 결국 포인터 변수에 N을 더하거나 뺄 경우는 '포인터가 가리키는 데이터형 x N'만큼 주소를 더하거나 뺀 값이 된다.

 이 시점에서 배열과 포인터의 관계에 대한 감이 조금 잡힐 것이다. 아래의 코드를 보자.
 int arr[5];
 int *p = &arr[0];
 int i;
 for(i=0;i<5;i++,p++)
 printf("%d\n",*p);
포인터 변수 p는 배열 arr의 첫 번째 원소의 주소 값을 가졌으므로 *p는 첫 번째 원소의 값을 가리키게 된다. 그러므로 p++가
 되면 앞서 말했듯이 int형 포인터 변수의 증감이므로 4바이트 이동해서 정확히 배열 arr의 다음 원소를 가리킨다. 즉, arr[1]을
가리키게 된다. 도식화 하면 아래와 같다.

 
         
0x1000                    0x1004                        0x1008                        0x100c                       0x1010

  ↑                      ↗                               ↗                              ↗                              ↗
                                                               p

 배열의 이름은 배열의 시작 주소가 된다. 즉, int arr[5];에서 인덱스 없이 arr는 배열의 시작 주소이다. 이 말은 arr는 arr[0]의
주소와 같다는 것이다. 따라서 arr==&arr[0]=true가 된다. 여기서 배열의 이름인 arr은 주소이므로 포인터처럼 이용할 수 있다. arr+1이 되면 앞서 설명했듯이 arr[1]을 가리키게 되므로 *(arr+1)은 arr[1]의 값을 나타낸다.
 &arr[i] == arr+i;
 arr[i] == *(arr+i);

이것을 이용하여 배열을 포인터로 가리키면 포인터를 배열처럼 사용할 수 있다.
 int arr[5];
 int *p = arr;
 arr는 주소이므로 포인터 변수에 대입이 가능하다.
 여기서 p+1을 하면 arr[0](=arr)을 가리키던 것이 arr[1]을 가리키게 된다. 그리고 이것을 &p[1]로 표현할 수 있다.
 *(p+i) == p[i];
 p+i == &p[i];
 이렇게 포인터 변수를 배열 이름인 것처럼 사용할 수 있다.

 하지만 배열과 포인터의 중요한 차이점이 있다.
배열은 일단 메모리에 할당되면 시작 주소를 변경할 수 없다.
 int a[5];
 int b[5];
 a=b; //배열 a의 시작 주소를 배열 b의 시작 주소로 변경하려고 하면 컴파일 에러 발생
 a++; //배열 a의 시작 주소를 증가시키므로 (4바이트만큼 이동) 컴파일 에러

 반면 포인터 변수는 변수이므로 보관된 주소를 변경할 수 있다.
 int a[5];
 int b[5];
 int* p=a; //포인터 p를 배열 a의 시작 주소로 초기화
 p=b; //포인터 변수 p에 배열 b의 주소를 저장, p는 b를 가리킴
 p++; //포인터 p를 1만큼 증가시켜 b[1]을 가리킴

2.포인터와 문자열
 'A', 3.14와 같은 리터럴 상수는 메모리에 저장하지 않고 필요할 때마다 CPU 레지스터에 만들어지고 사용 후에 없어지는
임시 값(temporary value)이다. 하지만 CPU 레지스터는 보통 크기가 정해져 있는데 문자열 리터럴은 길이가 정해지지
않아 특별히 메모리에 보관해두고 사용한다. 하지만 리터럴 상수이므로 값을 읽어볼 수만 있고 변경할 수 없는 메모리
영역에 존재하며 "abc";는 실제로 문자열 리터럴 주소를 의미한다.
 char *p = "abc"; //p에 문자열 리터럴 주소를 보관
 p[0] = 'A'; //문자열 리터럴은 변경 불가
 strcpy(p,"hello"); //문자열 리터럴은 변경불가
 p="hello"; //포인터 변수 p에 다른 문자열 주소 대입은 가능

 문자열을 비교할 때 strcmp()를 쓰는 이유를 이제 알 수 있다.
 p == "abc"는 문자열의 주소를 비교하는 것이다. 당연히 주소는 다르다. 따라서 strcmp()를 이용해서 문자열의 내용을
비교하는 것이다.

3.const 포인터
 const char* p; //포인터가 가리키는 값 변경 불가
 char* const p; //포인터 변수 자신의 값(주소) 변경 불가
 const char* const p; //가리키는 값, 주소 둘 다 변경 불가

'프로그래밍 > C' 카테고리의 다른 글

typedef  (0) 2011.01.22
공용체와 열거체  (0) 2011.01.19
구조체  (0) 2010.10.23
포인터 (Pointer)  (0) 2010.10.09
선택정렬 (Selection Sort)  (0) 2010.10.09
배열 (array)  (0) 2010.10.09
비트 연산자  (0) 2010.10.03
1.기본 개념
 포인터는 다른 변수의 주소(address)를 저장하는 변수이다. 즉, 포인터 변수는 변수의 값으로 주소를 저장한다.

 형식 : 데이터형 *변수명;
 ex) char *ch; //또는 char* ch;
       int *num; //또는 int * num;

 위와 같이 데이터형을 쓰고 *을 쓰고 변수명을 적어준다. char*은 char형 변수의 주소, int*은 int형 변수의 주소를 저장한다는
의미이다. 여기서 포인터 변수는 포인터형에 관계없이 항상 크기가 4바이트다. char*형 변수, int*형 변수에 관계없이 "주소"를
저장하기 때문에 4바이트(32비트 플랫폼 기준) 크기를 갖는다. 하지만 포인터 변수가 가리키는 변수는 char형은 1바이트,
int는 4바이트와 같이 각 데이터형에 알맞은 크기를 가진다.

 #include <stdio.h>
 int main(void)
{
 char x;
 char *p = &x;

printf("char 의 크기 = %d\n",sizeof(x)); //char의 크기는 1바이트이므로 1이 출력됨
printf("char*의 크기 = %d\n",sizeof(p)); //char*의 크기는 4바이트이므로 4가 출력됨

return 0;
}

 포인터 변수 선언 시 유의할 점은 다수의 포인터 변수 선언 시에 int* a,b;라고 하면 int *a; int b;로 선언된 것과 같다.
 따라서 int *a, *b; 이렇게 포인터 변수마다 *을 써주어야 한다.

2.포인터의 사용
 포인터 변수를 사용하기 위해서 일단 두 가지 연산자가 필요하다. 첫 번재는 주소 구하기 연산자
(address of operator)
로 변수 이름 앞에 &을 사용하여 변수의 주소를 구할 수 있다.

 int x;
 int *p;
 p = &x; //x의 주소를 포인터 변수 p에 대입

 &연산자는 주소를 구하는 것이므로 상수나 수식에는 사용이 불가능하며 변수에만 사용이 가능하다.
그리고 반드시 int형 변수의 주소는 int*형의 변수에 저장해야 하며 다른 형의 변수에 저장하면 컴파일 경고가 발생한다.
 두 번째는 간접 참조 연산자(indirection operator)이다. 포인터 변수 앞에 *을 사용하면 포인터 변수가 가리키는
변수의 값을 읽어오거나 변경할 수 있다.

 int x;
 int *p = &x; //x의 주소를 포인터 변수 p에 대입
 *p = 10; //p가 가리키는 변수, 즉 x에 10을 대입

아래의 코드로 공부한 내용을 직접 확인해보자.

#include <stdio.h>
int main(void)
{
 int x;
 int *p = &x;
 *p = 10; //x에 10을 대입

 printf("x =%d\n",x); //x의 값인 10이 출력됨
 printf("*p =%d\n",*p); //p가 가리키는 변수 x의 값인 10이 출력됨

 printf("&x =%d\n",&x); //변수 x의 주소 값이 출력됨
 printf("p =%d\n",p); //p는 변수 x의 주소 값을 저장하고 있으므로 변수 x의 주소 값이 출력됨

 printf("&p =%d\n",&p); //포인터 변수 p의 주소 값이 출력됨

 return 0;

 포인터 변수 p 역시 변수이므로 메모리에 할당된 공간이 있고 그에 대한 주소를 가지므로 printf("&p= %d\n",&p);에서는
포인터 변수 p가 할당받은 메모리의 주소 값이 출력된다.

※ 이중 포인터(double pointer)
 int x;
 int *p = &x;
 int **dp = &p;
 **dp = 10;

//즉, *(*dp) = *(p) =  x = 10

삼중, 사중 포인터도 만들 수는 있으나 잘 사용되지 않는다.

3.포인터 사용 시 주의사항
 앞서 말했듯이 포인터 변수의 데이터형은 포인터 변수가 가리키는 변수의 데이터형과 반드시 일치해야 한다.
이유를 살펴보면 다음과 같다.

 short a;
 int *p = &a;
 *p=10;
위와 같은 선언 시에 포인터 변수 p는 자신이 가리키는 곳에 int형 변수가 있다고 가정하는데 실제로는 short형 변수인 a가
있기 때문에 short형 변수의 크기인 2바이트에 추가로 그다음 주소의 2바이트까지 합쳐서 총 4바이트, 즉 int형 변수의
크기로 10을 저장한다. 그렇게 되면 a 변수뿐 아니라 다음 주소의 값까지 변경되어서 실행 에러가 발생한다.
 또다른 주의사항은 포인터 변수를 초기화 하지 않아 쓰레기 값을 가지고 있을 때이다. 포인터 변수도 변수이므로
초기화 해주지 않으면 쓰레기 값을 갖는다.
 int *p;
 *p = 10;
 위와 같은 선언 시 p가 쓰레기 값을 가지고 있을 때는 10을 메모리의 어느 위치에 저장하게 될지 모른다. 따라서 심각한 문제를 일으킬 수도 있다.
 그렇다면 초기화하는 방법은 무엇일까? 포인터가 아무것도 가리키지 않을 때는 변수에 0번지를 저장하고, 이것을
널 포인터(null pointer)라고 부른다.
 int *p = NULL; //널 포인터로 초기화

 if(p!=NULL) //널 포인터가 아닌지 확인 후에 사용
 *p=10;

 if(p) //0이 아닌 값은 참이므로 이렇게 확인하는 방법도 가능
 *p=10;

'프로그래밍 > C' 카테고리의 다른 글

공용체와 열거체  (0) 2011.01.19
구조체  (0) 2010.10.23
배열과 포인터의 관계  (0) 2010.10.09
선택정렬 (Selection Sort)  (0) 2010.10.09
배열 (array)  (0) 2010.10.09
비트 연산자  (0) 2010.10.03
삼각형 출력하기  (0) 2010.10.03

+ Recent posts