포인터의 크기는 애플리케이션의 호환성과 다른 환경으로의 이식 가능성을 고민할때 문제가 된다. 최근에 널리 사용되는 대부분의 운영체제 환경에서 포인터의 크기는 일반적으로 포인터 타입에 상관없다. 예를 들면 char에대한 포인터는 구조체에 대한 포이터와 크기가 같다. C표준에서 모든 데이터 타입에 대한 포인터의 크기가 같아야 한다고 명시하고 있지는 않지만, 일반적으로 포인터의 크기는 동일하다. 하지만 함수에 대한 포인터와 데이터에 대한 포인터의 크기는 다를 수도 있다.



포인터의 크기는 사용하는 장비와 컴파일러에 따라 다르다. 예를 들어 윈도우버전에서 포인터의 크기는 32 또는 64bit이며 오래된 dos 그리고 왼도우 3.1 os에서 포인터는 16또는 32비트다.




1. 메모리 모델

- 64bit 컴퓨터 도입으로 C기본 데이터 타입의 메모리 크기 차이가 활실해졌다. 컴퓨터와 컴파일러들은 c 기본 데이터 타입의 메모리 할당을 위해 저 마다 다른 옵션의 메모리 모델을 가지고 있다. 이 메모리 모델들을 설명하기 위해 일반적으로 아래와 같은 표기법을 사용한다.



I In L Ln LL LLn P Pn



각 대문자는 정수(Integer), 롱(Long), 포인터(Pointer)에 해당하며, 각 소문자는 타입에 할당된 비트 수다. 


 C Data Type

LP64 

ILP64 

LLP64 

ILP32 

LP32 

char 

short

16 

16 

16 

16 

16 

__int32 

 

32 

 

 

 

int 

32 

64 

32 

32 

16 

long 

64 

64 

32 

32 

32 

long long 

 

 

64 

 

 

pointer 

64 

64 

64 

32 

32 




2. 사전 정의된 포인터 관련 데이터 타입

- 포인터를 다룰 때 다음 네가지의 사전 정의된 데이터 타입이 종종 사용된다


1) size_t -> 안전한 크기 타입 제공을 위해 사용

2) ptrdiff_t -> 포인터 연산을 처리하기 위해 사용

3) intptr->t와 uintptr_t -> 포인터 주소를 저장하기 위해 사용



3. size_t 타입에 이해

- size_t 타입은 c언어에서 임의의 객체가 가질 수 있는 최대 크기를 나타낸다. 크기를 표현하는데 음수의 사용은 의미가 없기 때문에 size_t는 부호 없느 ㄴ정수를 사용한다. size_t 타입을 사용하은 이유는 시스템에서 주소 지정이 가능한 메로리 영역과 일치하는 크기를 선언하는 이식 가능한 방법을 제공하기 위해서이다. 


malloc 함수와 strlen 함수가 size_t타입을 반환하는 인자로 사용되는 대표적인 함수이다. 일반적으로 32 system에서는 32bit, 64 system에서는 64bit이다. 



4. 포인터와 sizeof연산자 사용하기

- 포인터의 크기를 확인하는데 sizeof 연산자를 사용할 수 있다. 아래 코드는 char 포인터에 대한 크기를 출력한다.


printf("Size of *char: %d", sizeof(char*));


결과:

size of *char: 4


함수 포인터의 크기는 다를 수 있다. 보통의 주어진 환경의 운영체제와 컴파일러 조합에따라 결정된다 많은 컴파일러는 32비트나 64비트 애플리케이션 생성을 지원하며 같은 프로그램도 컴파일 옵션에 따라 다른 포인터 크기를 가진다. 



5. intptr_t uintptr_t 사용하기

- intptr_t와 uintptr_t 타입은 포인터의 주소를 저장하는데 사용된다. 이 두 타입은 다른 환경으로 이식이 가능하고 안전한 포인터 선언 방법을 제공하며, 시스템 내부에서 사용하는 포인터와 같은 크기다. 포인터를 정수 표현으로 변환할때 유용하게 사용할수 있다. 


uintptr_t타입은 intptr_t 타입의 부호 없는 버전에 해당된ㄷ. 대부분의 연산에서 intptr_t 타입을 사용하는 것이 좋으며, uintptr_t 타입은 intptr_t 타입만큼 유연하지 않고 사용이 제한적이다. 아래는 intptr_t 타입의 사용 방법을 보여준다.


int num;

intptr_t *pi = #


다음의 코드를 실행하면 문법 에러가 발생한다.


uintptr_t *pu = #


error: invalid converstio nfrom 'int*' to 'uintptr_t* {aka unsigned int*}'


위의 에러는 타입이 맞지 않아 생기는 에러이다. 캐스팅을 해주면 정상 할당된다.


uintptr_t *pu = (uinttpr_t *)#

'C' 카테고리의 다른 글

[C언어] 열거형 enum  (0) 2018.08.24
[C언어] 구조체와 공용체 (struct, union)  (0) 2018.08.24
c언어 배열  (0) 2018.07.22
C언어 산술, 관계, 논리연산자  (0) 2018.07.22
C언어 포인터 기초 개념  (0) 2018.07.14

포인터는 C언어 전반에 많은 영향을 미치며 언어 자체에 많은 유연함을 제공한다. 포인터는 동적 메모리 할당의 매우 중요한 부부이기도 하며, 배열의 표기법과 밀접한 관련이 있다. 


오랫동안 포인터는 C언어 학습에 걸림돌이 되었는데 포인터의 기본적이 개념은 매우 간단하다. 포인터는 메모리 위치의 주소를 저장하는 변수이다. 하지만 포인터 연산자를 사용하고, 암호와도 같은 난해한 표기법을 식별하려고 할 때부터 이 개념이 급격하게 복잡해진다. 포인터를 이해하는 열쇠는 C프로그램에서 메모리가 어떻게 관리되는지 이해하는데 있다. 포인터는 메모리의 주소를 담고 있기 때문에 메모리가 구성되고 관리되는 방법을 이해하지 못한다면 포인터의 동작 방식을 이해하기 쉽지 않다. 그렇기에 메모리의 구성 방법을 확실하게 이해하고 나면 포인터를 이해가기가 쉽다.


1. 포인터와 메모리

컴파일된 C 프로그램은 다음의 세 종류의 메모리를 사용한다.



1) 정적/전역

 - 정적(STATIC)으로 선언된 변수들은 정적/전역 메모리에 할당된다. 전역(GLOBAL)변수들 또한 같은 미모레이 할당된다. 정적/전역 변수들은 프로그램이 시작될 때 할당되며 프로그램이 종료될 때까지 메모리 공간에 남아 있다. 모든 함수에서 접그할 수 있는 전역 변수와는 달리 정적 변수의 접근 범위는 해당 변수를 선언한 함수로 제한된다.


2) 자동/로컬

 - 자동 변수는 함수 안에서 선언되고 함수가 호출될 때 생성된다. 자동 변수의 접근 범위는 선언된 함수로 제한되며, 함수가 호출되는 동안에만 존재한다. 일반적으로 블록문 안ㅅ에서 선언된 변수의 범위는 해당 블록을 ㅗ제한된다.


3) 동적

 - 동적메모리는 힙메모리 영역에 할당되고 필요한 경우 해제된다. 포인터를 사용하여 할당된 메모리 영역을 참조하며, 포인터의 의해 접근이 제한된다. 메모리를 해제하지 않는 한 메모리에 존재한다.


 

접근 범위 

수명 

전역(GLOBAL) 

전체파일 

어플리케이션 실행 동안 유지 

정적(STATIC)

선언된 함수 

어플리케이션 실행 동안 유지 

자동(LOCAL) 

선언된 함수 

함수 실행 동안 유지 

동적(DYNAMIC) 

참조하는 포인터 

메모리 해제 전까지 유지 



포인터 변수는 다른 병수 및 객체 또는 함수의 메모리상 주소를 포함하고 있다. 여기서 객채란, malloc 함수 같은 메모리 할당 함삼수를 사용하여 할당된 메모리를 말한다. 포인터는 일반적으로 문자 타입 포인터와 같이 포인터가 가리킬 대상에서 따라 특정 타입을 가지도록 선언되며 정수, 문자, 문자열 또는 구조체와 같은 어떠한 c 데이터 타입도 될 수 있다. 그러나 포인터 자체는 포인터가 참조하는 데이터 타입의 속성을 가지지 않는다. 포인터는 단지 주소만을 가지고 있다.




2. 포인터를 잘 알아야 하는 이유

  • 빠르고 효율적인 코드 작성
  • 다양한 문제에 대한 효과적인 해결 방법 제공
  • 동적 메모리 할당 지원
  • 작고 간결한 표현의 사용
  • 큰 오버헤드 없이 데이터 구조를 포인터로 전달
  • 함수의 매게변수로 전달된 데이터 보호



3. 포인터를 사용할때 발생할 수 있는 문제점

  • 배열이나 데이터 구조의 경계를 넘는 접근
  • 소멸한 자동/로컬 변수의 참조
  • 할당 해제된 힙 메모리의 참조
  • 아직 할당되지 않은 포인터에 대한 역참조


4. 포인터 선언하기
- 포인터 변수는 데이터 타입과 별표 그리고 변수 이름을 순서대로 나열하여 선언한다. 

 int num;
 int *pi;


별표는 변수를 포인터로 선언하는 데도 사용되지만 두 수를 곱하거나 포인터를 역참조하는데 사용 된다. 포인터를 선언하는데 있어 다음과 같은 사항을 기억 해둬야한다. 초기화 하지 않은 메모리에 대한 포인터는 문제가 될 수 있다. 그런 포인터를 역참조하면 포인터가 유효한 주소를 가리지 않을 것이고, 설령 유효한 주소를 가리킨다 하더라도 그 주오세는 유효한 데이터가 없을 가능성이 높다. 유효하지 않은 주소라는 것은 프로그램의 접근이 허용되지 않는 주소를 의미한다. 이러한 유효하지 않은 주소에 대한 접근은 프로그램의 비정상적 종료의 원인이 되는데 수많은 심각한 문제를 일으킬수 있다.
  • pi에는 정수 변수의 주소가 할당되어야 한다.
  • 이 변수들은 초기화되지 않았으므로 쓰레기값을 가진다.
  • 포인터 구현 자체에는 포인터가 어떤 종류의 데이터를 참조하는지 포인터의 내용이 유효한지를 추측할 만한 내용이 포함되어 있지 않다.
  • 그러나 포인터의 타입은 지정되어 있고 포인터가 올바르게 사용되지 않으면 컴파일러는 오류를 표시한다.



5. 포인터 선언을 읽는 방법

 - 포인터 선언을 이해하기 쉽게 읽는 방법의 비결은 뒤에서부터 읽는 것이다. 


 const int *pci;


1) const int *pci;    -> 포인터 변수 pci

2) const int *pci;    -> 정수를 가리키는 포인터 변수 pci

3) const int *pci;    -> 상수 정수를 가리키는 포인터 변수 pci



6. 주소 연산자

- 주소 연산자(&)는 변수의 주소를 반환한다. 아래와 같이 주소연산자를 사용하여 pi 포인터를 num 변수의 주소로 초기화할 수 있다.

 

 int num = 0;

 int *pi = #


num변수가 100번지에 위치하고 있다면 포인터 변수 pi가 가리키는 주소는 100번지가 된다.


 num = 0

 pi = num


위와 같은 코드를 컴파일 할경우 컴파일러는 'error: invalid conversion from 'int' to 'int*'와 같은 에러 메세지를 반환할것이다.

변수 pi는 정수형 포인터 변수이고 num은 정수형 변수이다. 결과적으로 num 이갖고 있는 0이라는 값을 정수형 포인터 변수에 초기화 하려고 하는 과정에서 에러가 난다. 두 변수는 같은 크기의 바이트를 사용하지만(4byte) 같은 데이터 타입이 아니다. 그러나 포인터로 캐스팅하는 것은 가능하다.


 pi = (int *)num;


컴파일 하면 오류는 생성되지 않는다. 다만 포인터 변수가 가리키는 0번지는 대부분 운영체제에서 프로그램이 사용할 수 있는 유요한 주소가 아니다. 그러기에 비정상 종료가 될것이다.



7. 포인터값 출력하기

- 위 예제에서 사용한 변수들은 쉬운 설명을 위해 100과 104 같은 주소를 사용하고 있다고 쳤지만 아래의 코드를 실행해보면 다음 과 같은 결과를 볼 수 있다.


int main()

{

int num = 0;

int *pi = #


printf("address of num: %d value: %d\n", &num, num);

printf("address of pi: %d value: %d\n", &pi, pi);


    return 0;

}


결과 : 

address of num: 7339564 value: 0

address of pi: 7339552 value: 7339564



8. 간접지정 연산자로 포인터 역참조하기

- 간접지정 연산자(*)는 포인터 변수가 가리키는 위치의 값을 반환하며 '참조 연산자로 포인터 값의 참조'는 종종 포인터 역참조라 불린다. 다음 예제는 변수 num과 pi가 선언되고 초기화 되었다.


 int num = 5;

 int *pi =#


그리고 다음 코드에서 변수 num의 값 5를 출력하기 위해 간접지정 연산자를 사용한다.


 printf("%d\n", *pi);


그리고 또한 좌변값에(lvalue)에 참조 연산자의 결과를 사용할 수 있다. 좌변값은 할당 연사자의 좌측에 위치한 피연산자를 말한다. 

 

 *pi = 200;

 printf("%d", num) // 200 출력


포인터 변수 pi가 가리키는 정수(num)에 200을 할당한다. pi는 변수 num을 가리키고 있으며, 200이 변수 num에 할당된다.



9. 함수 포인터

- 포인터는 함수를 가리키도록 선언될 수 있다. 함수포인터 선언은 일반적인 포인터 표기법에 비해 다소 복잡하다. 


 void (*foo)();


이름이 foo이며 매개변수와 반환값이 없는 함수포인터 선언방법을 보여준다.



10. void 포인터

- void 포인터는 어떤 타입의 데이터도 참조할 수 있는 범용 포인터다. 아래의 void 포인터 선언의 예제가 있다.

 

 void *pv;


void 포인터 선언에 두가지 흥미로운 점

  • void 포인터는 char 포인터와 같은 표현과 메모리 정렬 방법을 사용한다.
  • void 포인터는 다른 포인터와 절대 같지 않다. 하지만 NULL 값이 할당된 두 개의 void 포인터는 서로 같다. void 포인터의 실제 동작은 시스템에 따라 다른다.

모든 포인터는 void 포인터에 할당될 수 있으며, 할당된 포인터는 다시 원래의 타입으로 캐스팅하여 사용할 수있다. 원래의 타입으로 캐스팅이일어나면 포인터의 값은 기존 값과 같게된다. 


int main()

{

int num;

int *pi = #

printf("value of pi:pp", pi);

void* pv = pi;

pi = (int *)pv;

printf("value of pi:pp", pi);


return 0;

}


결과: 

value of pi:006FF874
value of pi:006FF874

void 포인터는 데이터 타입의 포인터에 사용되며, 함수 포인터에는 사용되지 않는다. void 포인터에 sizeof 연산자가 사용될 수있다. 그러나 void 자체에는 sizeof 연산자를 사용할 수 없다.

size_t size = sizeof(void*);  // 유효함
size_t size = sizeof(void)    // 유효하지 않음



'C' 카테고리의 다른 글

[C언어] 열거형 enum  (0) 2018.08.24
[C언어] 구조체와 공용체 (struct, union)  (0) 2018.08.24
c언어 배열  (0) 2018.07.22
C언어 산술, 관계, 논리연산자  (0) 2018.07.22
C언어 포인터의 크기와 데이터 타입  (1) 2018.07.14

+ Recent posts