프로그래밍/C언어

포인터사용시 주의점2

콘파냐 2013. 6. 12. 18:13
반응형

일반적으로 포인터는 특정 변수를 가르킨다.

그리고 이 포인터가 가르키는 변수는 타입이 정해져서 그 변수가 담고있는 내용은 틀에 맞춰 끼워진내용이란말이다.

다시말해 형식이 정해져있는 내용이다.

예를들어 int형 변수를 가르키는 포인터는 그변수가 int형(4Byte)를 가르키기때문에 그에 맞춰 변수의 주소값과 변수의 타입명을 확실히 알고 값에 접근할 수가있다.

 

이제부터 쓰는 내용은 이런 확실함에 대한 이야기다.

내부적으로 컴파일러의 설계에 따라 달라질 수 가 있을지모르겠고, 난 전혀 컴파일러를 설계해보지 않았기때문에 경험적으로 말하는 것임을 미리 말해둔다.



char* str;

위선언은 무척 많이 보았을것이다.

위선언을 보고 동적할당을 해야겠구나. 라고 생각을 한다면, 어느정도 기본을 충실히 한 사람일 것이다.

만약 그렇지 않다면, 이 내용을 이해하는데 문제가 있을 수 있다.

str은 앞으로 동적할당을 해줘야한다.

char* str은 참 특수한 형이다. 난 문자열을 사용하기위해 이런형식을 왜 사용해야할까 답답하기도했다.

각설하고 int* a; 와 비교를 해보자. int형 변수의 주소를 담을 포인터 를 선언한 것이다.

 

str은 그 자체로 배열적인 성격도 지니고(배열명), 문자열적인 특성도 가진다.

char은 문자지만 포인터로 선언하면서 위의 두가지 속성을 가져버린것이다.

배열적성격은 문자들의 배열이란뜻이고, str이 배열명으로 사용되어 배열의 시작 주소를 가르킨다.

내부적으로 배열을 생성하고 각각요소에 1Byte할당하여 문자를 저장하여 전체적으로 문자열을 가르키도록 하는것이다. 그런데 여기서문제가 생긴다. 저장하려는 문자열의 길이가 상황에따라 달라질수 있기때문이다.

우리가 사용하는 int형은 정해진 4Byte내에서 모든걸 해결한다. 하지만 문자열은 그렇지 않다.

문자열의 길이에따라 바이트수의 편차가 심하게 차이가나기때문이다.

그렇기때문에 동적할당이 필요한것이다.

그리고 char* str; 에 값을 넣는 것은불안정하고, 단 선언과 동시에 초기화를 시켜준다면 상관없다.

예를들어 char* str = "dkfdskjfdf"; 이런식으로 말이다.

마치 선언과 동시에 전체를초기화해야한다는 배열의 성격과 일맥상통한다.

만약 선언만 한다면 동적할당을해야한다.(배열의 길이를 정하지않았기때문이다.)

배열의 길이를 정한경우(char str[10]; 이경우 동적할당없이 영문 10자안에서 안전하게 사용할수있다.

 

다음코드를 살펴보자.

int a;

printf("%d",&a);

printf("%d",&a+1);

 

원하는 결과가 나왔는가?

 

a변수의 타입을 double로 바꿔보자.

결과값을 다를지몰라도, 두 결과값들의 차이는 8(byte)가 된다.

굳이 배열로 선언하지 않는 형태의 변수라도 그 변수를 기준으로한 주소값의 증감은 자신의 타입만큼 이동하게된다.

그러나 포인터변수가 아닌이상 직점 a변수를 조작하여 &a+1주소의 값을 바꿀수는 없다.

우선 a자신의 주소값을 자신의 타입만큼 변경해야하는데 &a=&a+1라는 연산자체가 에러가 되기때문이다.

&a는 좌변값이 될 수가 없다.

이는 다음과 같은 에러를 발생시킨다.

lvalue required as left operand of assignment

 

좌변값에 대한 참고(작성중)

 

만약 바꾸고 싶다면 타입이 같은 포인터 변수를 선언하여 &a의 값을 대입한후 그 포인터 변수를 증가시킨뒤

값을 입력하면 될 것이다.

 

분명 동작을 한다. 부자연스럽지만,a를 기준으로 배열처럼 동작을 한다.

하지만 배열을 선언하면 명확해지고 간결해진다.

이런식의 사용은 혼란만 가져올 뿐이다.

!!단 이런 코드는 명확하지 않고 당장은 아니더라도 에러가 날 것이다.

실험결과 70번째(b+70)에서 에러가 났다.

 

 

한가지더 배열을 int a[10] 이라고 선언을 한 후에도 a[11]은 동작은 한다.하지만 코드가 길어지면 에러가 날 것이다. 왜냐면 위의 코드가 동작하는 원리와 같은 원리다. 

 

 

배열과 포인터의 관계를 확실히 이해한다면,위내용도 충분히 이해할 수 있다.

 

그럼 좀더 심오하게 c++로 가보자.

클래스의 객체를 생성하는 경우를 생각해보자.

class A;

...

...

 

A a;

위경우 정확히 생성된다.

A* a;

이경우 포인터객체다 타입은 알겠는데 몇개??  우리는c++에선 new 키워드로 동적할당을 한다.

A* a = new A[10];

이런식으로 할당하면 우리는 a[0],a[1],a[2],a[3],a[4]...a[9] 이렇게 10개의 객체가 생성된다.

A* a; 객체의 이런식의 선언 또한 별반 다르지않다.

 

기본적으로 포인터 객체를 선언하든,포인터 기본타입을 선언하든 컴파일러는 물어본다.

타입은 알겠는데 몇개?생성할꺼야? 그리고 중요한것은 위선언은 생성자의 호출이 일어나지않는다는 점이다.

a에 기존 생성된 객체변수의 주소값을 지정해서

a = &b; 이경우 포인터인 a가 변수 b를 가르키게된다. 타입과 개수를 알기때문에 문제없다.

 

Type* a;

이런 표현은 불완전한 표현이다.

간혹 이런표현을 한후 값을 직접 지정해주어서 에러가 안나는 경우가있다.

아마 위선언 앞뒤로 포인터선언을 한것이 있을 것이다.

이런경우 잠재적인 에러의 원인이 된다.

특히 이런경우 에러의 원인을 파악하기 힘들어 골머리를 썩힐 경우가 많다.

 

int* a;

int* b;

..

..

printf("%d", b);

 

젠장 에러가 안난다. int* a;를 주석처리해봐라 에러가난다. 에러의 형태도 잘 알아놓길바란다.

 

쉽게생각해서 동적할당이란 개수를 할당해주는것이다.물론 타입도 전달되지만...

A a = new A(); 이경우 물론 하나만 생성된다. 타입은 A클래스인것이다.

A a = new A[10](); 생성이가능하다.

 

A a = new A[10](3); 아래 에러메시지가난다.

ISO C++ forbids initialization in array new

 

배열을 생성하며 동시에 인자를 가지는 생성자를 호출하는 방법은 각각 하나씩 초기화하는 방법뿐인듯하다.

반응형