공부했던 자료 정리하는 용도입니다.

재배포, 수정하지 마세요.

 

 


 포인터 

  1. 포인터
    • 포인터 변수
    • 포인터 변수의 선언
      * 포인터 변수의 크기
    • 포인터 형(type)
    • 포인터 변수와 관련된 연산자
      1. & 연산자
      2. * 연산자
    • null 포인터
    • 포인터 대상의 const 선언
      * 상수를 가리키는 포인터인 경우
      * 포인터 자체가 상수인 경우
      * 포인터가 상수이면서 상수를 가리키는 경우
  2. 포인터와 배열
    • 포인터 연산
    • 문자열의 선언방식
    • 포인터 배열
      * 문자열 배열
  3. 포인터와 함수
    • 함수의 호출 방식
      * Call-by-value
      * Call-by-reference
    • 함수의 인자로 배열 전달
      * 인자로 배열 전달 시 배열의 크기
  4. 이중 포인터(더블 포인터)
    • 이중 포인터 선언과 사용
    • 포인터 변수 대상의 Call-by-reference
    • 포인터 배열과 포인터 배열의 포인터 type
    • 다중 포인터 변수
  5. 다차원 배열과 포인터
    • 2차원 배열의 이름과, 2차원 배열의 첫 번째 행
    • 2차원 배열의 증감 연산
    • 2차원 배열의 포인터
    • 함수 인자로 2차원 배열 전달
    • 다차원 배열에서 arr[i]와 *(arr + i)
    • 배열 포인터와 포인터 배열
    • main 함수의 인자
      * 인자의 형성 과정
      * char * argv[ ]
  6. 함수 포인터
    • 함수 포인터 변수 선언
  7. void 포인터

 


 

이중 포인터(더블 포인터)

  포인터 변수를 가리키는 또 다른 포인터 변수를 이중 포인터 또는 더블 포인터라 부른다. 포인터 변수는 종류에 상관없이 무조건 주소 값을 저장하는 변수인데, 어쨌든 변수이고 메모리에 할당되기 때문에 이를 대상으로도 ``&``연산이 가능하고, 이때 반환되는 주소 값은 더블 포인터 변수에 저장이 가능하다.

 

 

이중 포인터 선언과 사용

double num = 3.14;
double *ptr = #
double **dptr = &ptr;

*dptr = ... 	// *dptr은 포인터 변수 ptr을 의미
*(*dptr) = ... 	// *(*)dptr은 변수 num을 의미, **dptr로도 쓴다.

  더블 포인터는 싱글 포인터와 구분하기 위해서 ``*``를 2개 붙여 선언한다. 더블 포인터 이름에 ``*``을 하나만 붙이면 가리키는 싱글 포인터의 주소 값이 되고, ``**``처럼 두 개를 붙이면 해당 싱글 포인터가 가리키는 값이 된다. ``*(*dptr)``과같은 경우에는 괄호를 생략하고 ``**dptr``로 표현이 가능하며, 생략하는 것이 보다 일반적인 표현이다.

 

#include <stdio.h>

int main(void)
{
	double num = 3.14;
	double *ptr = &num;
	double **dptr = &ptr;
	double *ptr2;
	
	printf("%9p %9p \n", ptr, *dptr);
	printf("%9g %9g \n", num, **dptr);
	ptr2 = *dptr;	// ptr2 = ptr와 같은 문장
	*ptr = 10.99;
	printf("%9g %9g \n", num, **dptr); 
	
	return 0;
}

더블 포인터를 사용하는 예시이다.

 

포인터의 참조관계

12행까지 실행되면 포인터 변수의 참조 관계는 위와 같이 되며, 변수 ``num``에 접근하는 방법은 아래와 같이 총 4가지가 존재하게 된다. 

  1. **dptr = 10.1;
  2. *ptr =20.2;
  3. *ptr2 = 30.3;
  4. num = 40.4;

 

 

 

포인터 변수 대상의 Call-by-reference

#include <stdio.h>

void SwapIntPtr(int *p1, int *p2)
{
	int *temp = p1;
	p1 = p2;
	p2 = temp;
}

int main(void)
{
	int num1 = 10, num2 = 20;
	int *ptr1, *ptr2;
	ptr1 = &num1, ptr2 = &num2;
	printf("*ptr1, *ptr2 : %d %d \n", *ptr1, *ptr2);
	
	SwapIntPtr(ptr1, ptr2);
	printf("*ptr1, *ptr2 : %d %d \n", *ptr1, *ptr2);
	
	return 0;
}

  함수를 이용해서 싱글 포인터 변수의 값을 변경하는 경우 같은 싱글 포인터 변수를 사용하면 값을 변경하지 못한다. 위와 같은 경우에는 ``ptr1``과 ``ptr2``로 ``num1``과 ``num2``의 주소 값을 넘겨주었지만 함수를 실행하면 ``ptr1``과 ``ptr2``자체의 주소같이 넘어가는 게 아니라 이들 안에 저장된 값(``num1``과 ``num2``의 주소)과 같은 주소 값을 가진 포인터 변수 ``p1``, ``p2``가 새로 생겨난다. 그래서 ``p1``, ``p2``끼리는 값이 바뀌어도 원래의 포인터들인 ``ptr1``과 ``ptr2``의 주소 값은 바뀌지 않는다.

 

실행 결과



#include <stdio.h>

void SwapIntPtr(int **p1, int **p2)	// 더블 포인터 변수 선언
{
	int *temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}

int main(void)
{
	int num1 = 10, num2 = 20;
	int *ptr1, *ptr2;
	ptr1 = &num1, ptr2 = &num2;
	printf("*ptr1, *ptr2 : %d %d \n", *ptr1, *ptr2);
	
	SwapIntPtr(&ptr1, &ptr2);	// 싱글 포인터 변수의 주소값을 함수의 인자로 사용
	printf("*ptr1, *ptr2 : %d %d \n", *ptr1, *ptr2);
	
	return 0;
}

호출되는 함수의 매개변수를 더블 포인터 변수로 바꾸고, 인자로 싱글 포인터 자체의 주소 값을 넘겨야 값이 제대로 변환된다.

 

실행 결과



 

 

포인터 배열과 포인터 배열의 포인터 type

#include <stdio.h>

int main(void)
{
	int num1 = 10, num2 = 20, num3 = 30;
	int *ptr1 = &num1;
	int *ptr2 = &num2;
	int *ptr3 = &num3;
	
	int *ptrArr[] = {ptr1, ptr2, ptr3};	// 포인터 배열
	int **dptr= ptrArr;	// 포인터 배열을 가리키는 더블 포인터(포인터 배열의 자료형과 같음)
	
	printf("%d %d %d \n", *(ptrArr[0]), *(ptrArr[1]), *(ptrArr[2]));
	printf("%d %d %d \n", *(dptr[0]), *(dptr[1]), *(dptr[2]));	// 포인터 변수도 배열의 이름처럼 사용 가능
	
	return 0;
}

  배열의 포인터가 배열의 자료형과 같았던 것처럼 포인터 배열도 1차원 배열이기 때문에 포인터 배열의 자료형에 따라 포인터 형이 결정된다. 또한 14행처럼 포인터의 이름도 배열처럼 사용 가능하다.

 

실행 결과



 

 

다중 포인터 변수

#include <stdio.h>

int main(void)
{
	int num = 100;
	int *ptr = &num;	// 포인터 변수
	int **dptr = &ptr;	// 이중 포인터 변수
	int ***tptr = &dptr;	// 삼중 포인터 변수
	
	printf("%d %d \n", **dptr, ***tptr);
		
	return 0;
}

  포인터 변수를 선언할 때 ``*``연산자가 2개 이상 사용되는 포인터 변수를 다중 포인터 변수라고 한다. 보통 ``*``이 ``n``개 사용된 ``n``중 포인터라면 ``n - 1``중 포인터 변수를 가리키고, ``n - 1``중 포인터 변수의 주소 값을 저장하는 용도로 사용된다. 삼중 포인터부터는 사용되는 곳이 그렇게 많지 않기 때문에 삼중 포인터를 사용해야 하는 경우 포인터의 오용 및 남용은 아닌지, 잘못된 방식으로 접근하고 있는 것은 아닌지 확인해볼 필요가 있다.

 

 

 

'기타 > C' 카테고리의 다른 글

[C] 함수 포인터, void 포인터  (0) 2020.05.22
[C] 다차원 배열과 포인터  (0) 2020.05.19
[C] 포인터와 배열, 함수  (0) 2020.05.07
[C] 포인터  (0) 2020.05.05
[C] 배열 (1차원 배열, 다차원 배열, 가변 길이 배열)  (1) 2020.04.28

 

 

공부했던 자료 정리하는 용도입니다.

재배포, 수정하지 마세요.

 

 


 포인터 

  1. 포인터
    • 포인터 변수
    • 포인터 변수의 선언
      * 포인터 변수의 크기
    • 포인터 형(type)
    • 포인터 변수와 관련된 연산자
      1. & 연산자
      2. * 연산자
    • null 포인터
    • 포인터 대상의 const 선언
      * 상수를 가리키는 포인터인 경우
      * 포인터 자체가 상수인 경우
      * 포인터가 상수이면서 상수를 가리키는 경우
  2. 포인터와 배열
    • 포인터 연산
    • 문자열의 선언방식
    • 포인터 배열
      * 문자열 배열
  3. 포인터와 함수
    • 함수의 호출 방식
      * Call-by-value
      * Call-by-reference
    • 함수의 인자로 배열 전달
      * 인자로 배열 전달 시 배열의 크기
  4. 이중 포인터(더블 포인터)
    • 이중 포인터 선언과 사용
    • 포인터 변수 대상의 Call-by-reference
    • 포인터 배열과 포인터 배열의 포인터 type
    • 다중 포인터 변수
  5. 다차원 배열과 포인터
    • 2차원 배열의 이름과, 2차원 배열의 첫 번째 행
    • 2차원 배열의 증감 연산
    • 2차원 배열의 포인터
    • 함수 인자로 2차원 배열 전달
    • 다차원 배열에서 arr[i]와 *(arr + i)
    • 배열 포인터와 포인터 배열
    • main 함수의 인자
      * 인자의 형성 과정
      * char * argv[ ]
  6. 함수 포인터
    • 함수 포인터 변수 선언
  7. void 포인터

 


 

포인터와 배열

  배열도 포인터이다. 그러나 값을 바꿀 수 없는 '상수 형태의 포인터'이다.

 

 

포인터 연산

arr[i] == *(arr + i);

  ``type``형 포인터를 대상으로 ``n``의 크기만큼 값을 변경시키면 @@n × sizeof(type)@@의 크기만큼 주소 값이 증가/감소한다.(``int``형일 경우 ``4``씩, ``double``형일 경우 ``8``..) 그래서 위와 같이 배열일 경우 @@arr[i] == *(arr + i)@@이 성립한다.

 

 

 

 

문자열의 선언방식

   마지막에 null 문자가 삽입되는 문자열의 선언방식 

  1. 변수 형태의 문자열 : 배열 이용
  2. 상수 형태의 문자열 : ``char``형 포인터 변수 이용

 

//마지막에 null 문자가 삽입되는 문자열의 선언방식 
// 1. char형 배열 이용(변수 형태의 문자열,  문자열 변경 가능)
char str1[] = "String1";

// 2. char형 포인터 변수 이용(상수 형태의 문자열, 문자열 변경 불가)
char * str2 = "String2";

  ``str1``은 그 자체로 문자열 전체를 저장하는 배열이다. ``str2`` 처럼 선언하면 메모리 공간에 문자열 ``String``이 저장되고, 문자열의 첫 번째 문자 ``S``의 주소 값이 반환된다. 그리고 그 반환 값이 포인터 변수 ``str2``에 저장된다. ``str1``, ``str2`` 모두 문자열의 시작 주소 값을 가리키고 있다는 것에서는 동일하지만 ``str1``은 상수 형태의 포인터이기 때문에 가리키는 대상을 변경할 수 없는 반면, ``str2``는 가리키는 대상을 변경할 수 있다.  배열로 선언되는 문자열을 '변수 형태의 문자열'이라 하고 ``char``형 포인터 변수를 이용해서 선언한 문자열은 '상수 형태의 문자열'이라고 한다. 

 

  ##"##로 묶여서 표현되는 문자열은 그 형태에 상관없이 메모리 공간에 저장된 후 그 주소 값이 반환된다. 그래서 ##printf## 함수는 문자열이 아닌 문자열의 주소 값을 전달받는 함수이다. (인자가 ##char *## 형)

 

 

 

 

포인터 배열

#include <stdio.h>

int main(void)
{
	int num1 = 10, num2 = 20, num3 = 30;
	int * arr[3] = {&num1, &num2, &num3};
	
	printf("%d \n", *arr[0]);
	printf("%d \n", *arr[1]);
	printf("%d \n", *arr[2]);
	
	return 0;
}

포인터 변수로 이뤄져 주소 값의 저장이 가능한 배열을 포인터 배열이라고 한다.

 

 

 

  ■ 문자열 배열

#include <stdio.h>

int main(void)
{
	char * strArr[3] = {"Simple", "String", "Array"};
	
	printf("%s \n", strArr[0]);
	printf("%s \n", strArr[1]);
	printf("%s \n", strArr[2]);
	
	return 0;
}

  문자열 배열은 문자열의 주소 값을 저장할 수 있는 배열로서 사실상 ``char *``형 포인터 배열이다. ``"``로 묶여서 표현되는 문자열은 메모리 공간에 저장된 후, 주소 값이 반환되므로 실제로는 ``strArr``에 각 문자열의 주소 값들이 들어있는 것이다. 

 

 

 

 

 


포인터와 함수

함수의 호출 방식

 

   함수의 호출 방식 

  • Call-by-value : 을 전달하는 형태의 함수 호출
  • Call-by-reference : 주소 값을 전달하는 형태의 함수 호출

 

  ■ Call-by-value

#include <stdio.h>

void Swap(int n1, int n2)
{
	int temp = n1;
	n1 = n2;
	n2 = temp;
	printf("n1 n2 : %d %d \n", n1, n2);
}

int main(void)
{
	int num1 = 10;
	int num2 = 20;
	printf("num1 num2 : %d %d \n", num1, num2);
	
	Swap(num1, num2);
	printf("num1 num2 : %d %d \n", num1, num2);
	
	return 0;
}

함수 호출 시 전달되는 인자의 값은 매개변수에 복사가 되는데 이런 방식을 Call-by-value라고 한다.

그래서 위의 예시의 경우 ``Swap``함수를 호출해서 ``main``함수의 ``num1``과 ``num2``값을 서로 바꾸려 했지만 바뀌지 않았다.

 

실행 결과

 

 

 

  ■ Call-by-reference

#include <stdio.h>

void Swap(int * n1, int * n2)	// 매개변수의 주소값을 인자로 받음
{
	int temp = *n1;
	*n1 = *n2;
	*n2 = temp;
}

int main(void)
{
	int num1 = 10;
	int num2 = 20;
	printf("num1 num2 : %d %d \n", num1, num2);
	
	Swap(&num1, &num2);	// 매개변수가 포인터이므로 주소값 전달
	printf("num1 num2 : %d %d \n", num1, num2);
	
	return 0;
}

앞의 예시에서 함수로 실제 변수값을 바꾸고 싶다면 변수의 주소 값을 인자로 받아서 처리해야 된다.

이런 방식을 Call-by-reference라고 한다.

 

실행 결과

 

 

 

 

함수의 인자로 배열 전달

void ShowArayElem(int * param, int len) {...}
void AddArayElem(int * param, int len, int add) {...}

// 배열의 주소값이 인자로 전달될 때에는 주로 아래와 같이 선언된다.
void ShowArayElem(int param[], int len) {...}
void AddArayElem(int param[], int len, int add) {...}

  함수의 매개변수로 배열을 선언할 수 없지만 배열의 주소 값을 전달하는 것은 가능하다. 일반적으로 배열의 주소 값이 인자로 전달될 때에는 배열인 것을 강조하기 위해 ``param[ ]``과 같은 표현을 더 많이 사용한다. (매개 변수일 때만 서로 변환 가능)

 

int arr[3] = {1, 2, 3};
int * ptr = arr;	//int ptr[] = arr;로 대체 불가능

매개변수가 아닌 경우에는 대체할 수 없다.

 

 

 

  ■ 인자로 배열 전달 시 배열의 크기

#include <stdio.h>

void ShowArayElem(int * param, int len)
{
	for(int i = 0 ; i < len ; i++)
		printf("%d ", param[i]);
	printf("\n");
}

int main(void)
{
	int arr1[3] = {1, 2, 3};
	int arr2[5] = {4, 5, 6, 7, 8};
	ShowArayElem(arr1, sizeof(arr1) / sizeof(int));
	ShowArayElem(arr2, sizeof(arr2) / sizeof(int));
	
	return 0;
}

  배열의 주소 값을 인자로 전달받는 매개변수는 포인터 변수이기 때문에 이를 대상으로 ``sizeof``연산을 할 경우 배열의 크기가 아닌 포인터 변수의 크기가 반환된다. 따라서 배열의 크기를 알 수 없기 때문에 배열의 크기나 길이 정보도 함께 인자로 전달해야 한다.

 

 

 

'기타 > C' 카테고리의 다른 글

[C] 다차원 배열과 포인터  (0) 2020.05.19
[C]이중 포인터(더블 포인터)  (0) 2020.05.12
[C] 포인터  (0) 2020.05.05
[C] 배열 (1차원 배열, 다차원 배열, 가변 길이 배열)  (1) 2020.04.28
[C] 함수  (0) 2020.04.27

 

 

공부했던 자료 정리하는 용도입니다.

재배포, 수정하지 마세요.

 

 


 포인터 

  1. 포인터
    • 포인터 변수
    • 포인터 변수의 선언
      * 포인터 변수의 크기
    • 포인터 형(type)
    • 포인터 변수와 관련된 연산자
      1. & 연산자
      2. * 연산자
    • null 포인터
    • 포인터 대상의 const 선언
      * 상수를 가리키는 포인터인 경우
      * 포인터 자체가 상수인 경우
      * 포인터가 상수이면서 상수를 가리키는 경우
  2. 포인터와 배열
    • 포인터 연산
    • 문자열의 선언방식
    • 포인터 배열
      * 문자열 배열
  3. 포인터와 함수
    • 함수의 호출 방식
      * Call-by-value
      * Call-by-reference
    • 함수의 인자로 배열 전달
      * 인자로 배열 전달 시 배열의 크기
  4. 이중 포인터(더블 포인터)
    • 이중 포인터 선언과 사용
    • 포인터 변수 대상의 Call-by-reference
    • 포인터 배열과 포인터 배열의 포인터 type
    • 다중 포인터 변수
  5. 다차원 배열과 포인터
    • 2차원 배열의 이름과, 2차원 배열의 첫 번째 행
    • 2차원 배열의 증감 연산
    • 2차원 배열의 포인터
    • 함수 인자로 2차원 배열 전달
    • 다차원 배열에서 arr[i]와 *(arr + i)
    • 배열 포인터와 포인터 배열
    • main 함수의 인자
      * 인자의 형성 과정
      * char * argv[ ]
  6. 함수 포인터
    • 함수 포인터 변수 선언
  7. void 포인터

 


 

포인터

  • 포인터를 이용하면 메모리에 직접 접근이 가능하다. 그래서 C언어가 Low 레벨 언어의 특성을 지닌다고 말한다. 
  • 시스템이 몇 비트인지에 따라 메모리 주소의 범위도 달라진다. ``sizeof(sizeof(포인터), sizeof(자료형 *))``로 포인터의 크기를 구해보면 32 bit에서는 4 byte, 64 bit에서는 8 byte가 나온다.

    32 bit : 16진수 8자리

    0x00000000 ~ 0xFFFFFFFF

    64 bit : 16진수 16자리

    8자리씩 끊어서 백틱을 붙이기도 한다. ex) 0x00000000`00000000
    0x0000000000000000 ~ 0xFFFFFFFFFFFFFFFF

 

 

  ■ 리눅스, OS X에서 %p

  리눅스나 OS X에서 서식 지정자 ``%p``를 사용하면 메모리 주소 앞에 0x가 붙고 A~F는 소문자로 출력된다. 또한 높은 자리수의 0은 생략된다.
  ex) 008AF7FC → 0x8af7fc

 

 

포인터 변수

  • 포인터 변수는 메모리의 주소 값을 저장하기 위한 변수이다.
    C언어는 시작 번지만을 가지고 위치를 표현한다. (자료형에 따라 끝 위치가 어딘지 계산이 가능하기 때문)
  • 포인터는 '변수 형태의 포인터'와 '상수 형태의 포인터'를 어우르는 표현이다. (대부분의 경우 포인터 변수를 의미)

 

 

 

포인터 변수 선언

int * pnum1;		// int형 변수를 가리키는 pnum1 선언
unsigned int * pnum2;	// unsigned int형 변수를 가리키는 pnum2 선언

// *의 위치는 상관 X (아래 3문장은 모두 동일한 의미)
double * ptr;
double* ptr;
double *ptr;

(주소 값은 동일한 시스템에서 그 크기가 동일하며 모두 정수의 형태를 띄지만) 가리키는 변수의 자료형에 따라서 선언하는 방법이 달라진다.

 

 

  ■ 포인터 변수의 크기

포인터와 변수의 참조관계

  포인터 변수의 크기는 운영체제마다 다르다. ``32bit`` 시스템에서는 주소 값을 ``32bit``로 표현하기 때문에 포인터 변수의 크기가 ``4byte``인 반면, ``64bit`` 시스템에서는 주소 값을 ``64bit``로 표현하기 때문에 포인터 변수의 크기가 ``8byte``이다.(주소 값의 크기와 포인터 변수의 크기는 같다.) 그러나 ##64bit## 시스템에서 동작하는 컴파일러의 경우 ##64bit##로 컴파일을 하지만 ##32bit##로도 컴파일을 할 수 있다. 따라서 포인터 변수 대상의 ##sizeof## 연산의 결과로 ##8##을 확인하려면 ##64bit## 시스템상에서 ##64bit##로 컴파일해야 한다. 

 

 

 

포인터 형(type)

  포인터 변수의 선언 및 구분에 사용되는 ``int *``, ``char *``, ``double *`` 등을 가리켜 포인터 형(type)이라고 한다. 포인터 변수도 값을 저장하는 변수이기 때문에 포인터 형도 자료형의 범주에 포함시키기도 한다. 포인터형은 메모리 공간을 참조하는 기준이 되기 때문에 포인터 형에 맞지 않는 자료형의 변수를 참조(``int *`` 형 포인터 변수가 ``double``형 변수를 참조한다던지)하면 안 된다(경고 메시지는 출력되지만 컴파일 에러가 발생하지는 X)

 

 

 

 

포인터 변수와 관련된 연산자

  ■ & 연산자

int num = 6;
int * pnum = &num;	// num의 주소값을 반환해서 포인터 변수 pnum을 초기화

  ``&``연산자는 오른쪽에 등장하는 피연산자의 주소 값을 반환하는 연산자이다. 피연산자는 변수여야 하며, 상수는 피연산자가 될 수 없다. 또한 변수의 자료형에 맞지 않는 포인터 변수의 선언은 문제가 될 수 있다.(컴파일 에러는 발생하지 않지만 경고 메시지는 발생하며 포인터 관련 ##*##연산 시 문제가 발생한다.)

 

 

 

  ■ * 연산자(역참조 연산자)

int num = 10;
int * pnum = &num;	// 포인터 변수 pnum이 변수 num을 가리키게 하는 문장
*pnum = 20;		// pnum이 가리키는 변수에 20을 저장
printf("%d", *pnum);	// pnum이 가리키는 변수를 출력

``*`` 연산자는 포인터가 가리키는 메모리 공간에 접근할 때 사용하는 연산자이다.

위의 경우 사실상 ``*pnum``은 포인터 변수 ``pnum``이 가리키는 변수 ``num``을 의미한다.

 

  • 포인터의 자료형과 역참조
char *cPtr;	// char형 포인터
*cPtra = 'a';	// 1 byte(char의 크기)만큼 역참조하여 값을 저장

short *sPtr;	// short형 포인터
*sPtr1 = 100;	// 2 byte(short의 크기)만큼 역참조하여 값을 저장

int *iPtr;	// int형 포인터
*nPtr = 100;	// 4 byte(int의 크기)만큼 역참조하여 값을 저장

long long *lPtr;    // long long형 포인터 선언
*lPtr = 100;	    // 8 byte(long long의 크기)만큼 역참조하여 값을 저장

포인터는 포인터를 선언한 자료형의 크기만큼 역참조 하는 크기가 결정된다.

 

 

null 포인터

// 아래 두 문장은 같은의미이다.
int * ptr1 = 0;
int * ptr2 = NULL:	// NULL 은 0을 의미
  • 값 ``0``을 널 포인터라고 하는데, ``0``번지가 아닌 아무 데도 가리키지 않는다는 의미이다. 
  • 키워드 ``NULL``도 마찬가지로 널 포인터를 의미하며, 실제로 이는 상수 ``0``으로 정의되어 있다.
  • 아무것도 가리키지 않는 상태이기 때문에 역참조는 할 수 없다.

  포인터 변수는 선언만 하고 초기화하지 않으면 쓰레기 값으로 초기화된다. 우선 선언만 해놓고 이후에 주소를 가리키게 하려면 위와 같이 초기화하면 된다. null 포인터를 이용한 ``*`` 연산은 메모리 공간에 어떠한 영향도 미치지 않으며, 프로그램이 멈추는 현상은 동일하게 일어나지만, 잘못된 메모리 접근에 대해 보호장치가 없는 운영체제에서도 시스템에 치명적인 영향을 주지 않는다.

 

 

 

 

포인터 대상의 const 선언

//1. 상수를 가리키는 포인터(pointer to constant)인 경우 : 메모리 주소에 저장된 값을 변경할 수 없다는 의미
const int num = 10;
const int *numPtr;	// int형 상수를 가리키는 포인터. int const *numPtr도 같음

numPtr = &num1;
*numPtr = 20;		// error. num1이 상수이므로 역참조로 값 변경 불가


//2. 포인터 자체가 상수(constant pointer)인 경우: 메모리 주소를 변경할 수 없다는 의미
int num1 = 10;
int num2 = 20;
int * const numPtr = &num1;	// int형 포인터 상수

numPtr = &num2;		// error. 포인터(메모리 주소) 변경 불가


//3. 포인터가 상수이면서 상수를 가리키는 상황(constant pointer to constant) : 메모리 주소에 저장된 값과 메모리 주소 모두 변경할 수 없다는 의미
const int num1 = 10;
const int num2 = 20;
const int * const numPtr = &num1;	// int형 상수를 가리키는 포인터 상수. int const * const numPtr도 같음

*numPtr = 30;		// error. num1이 상수이므로 역참조로 값 변경 불가
numPtr = &num2;		// error. 포인터(메모리 주소) 변경 불가

포인터에도 ``const``를 붙일 수 있는데, 위치에 따라 특성이 달라진다.

  1. 상수를 가리키는 포인터(pointer to constant)인 경우 : 메모리 주소에 저장된 을 변경할 수 없다는 의미
  2. 포인터 자체가 상수인 경우(constant pointer)인 경우 : 메모리 주소를 변경할 수 없다는 의미
  3. 포인터가 상수이면서 상수를 가리키는 상황(constant pointer to constant) : 메모리 주소에 저장된 과 메모리 주소 둘 다 변경할 수 없다는 의미

 

 

  ■ 상수를 가리키는 포인터(pointer to constant)인 경우

int num = 20;
const int * ptr = &num;
*ptr = 30	// error
num = 40;	// 가능

  ``const``를 맨 앞에 선언하면 '포인터 변수를 이용해 포인터 변수가 가리키는 값의 변경을 허용하지 않겠다'라는 의미이다. (값의 변경 방법 제한) 위의 예시의 경우 ``ptr``을 이용해 ``ptr``이 가리키는 변수의 값을 변경할 수 없다. 그러나 포인터 변수 @@ptr@@이 가리키는 변수가 상수화 되는 것은 아니다. 위의 예시처럼 포인터 변수를 이용하지 않는 ``num = 40;`` 문장은 허용된다.

 

 

  ■ 포인터 자체가 상수(constant pointer)인 경우

int num1 = 20;
int num2 = 30;
int * const ptr = &num1;
ptr = &num2;	// error
*ptr = 40;	// 가능

  포인터 변수 앞에 ``const``가 오면 포인터 변수가 상수가 된다. (한번 주소 값이 저장되면 변경 불가) 포인터 변수가 상수가 되는 것이라 포인터가 가리키는 대상에 저장된 값을 변경하는 것은 가능하다.

 

 

  ■ 포인터가 상수이면서 상수를 가리키는 상황(constant pointer to constant)인 경우

const int * const ptr = &num;

  앞서 말한 두 가지 형태의 ``const``를 동시에 선언하는 것도 가능하다. 동시에 선언하면 해당 포인터 변수로 포인터 변수가 참조하는 변수의 값도 변경할 수 없고, 포인터 변수를 변경하는 것도 불가능하다. 

 

 

 

'기타 > C' 카테고리의 다른 글

[C]이중 포인터(더블 포인터)  (0) 2020.05.12
[C] 포인터와 배열, 함수  (0) 2020.05.07
[C] 배열 (1차원 배열, 다차원 배열, 가변 길이 배열)  (1) 2020.04.28
[C] 함수  (0) 2020.04.27
[C] 조건문, 반복문  (0) 2020.04.16

 

 

공부했던 자료 정리하는 용도입니다.

재배포, 수정하지 마세요.

 

 


 배열 

  1. 1차원 배열
    • 배열 선언 및 초기화
    • 배열 접근
      * 배열 접근 시 주의사항
    • 배열의 크기와 길이
      * 배열의 최대 크기
    • 배열을 이용한 문자열 변수의 표현
      * 공백과 null(\0)
      * scanf 함수를 이용한 문자열의 입력
  2. 다차원 배열
    • 2차원 배열 선언 및 초기화
    • 2차원 배열 접근
      * 2차원 배열의 크기, 행, 열 길이
  3. 가변 길이 배열(Variable-Length Array, VLA)

 


 

배열

  다수의 데이터를 저장하고 처리하는 경우에 유용하게 사용할 수 있다.

 

 

배열 선언 및 초기화

[자료형] [배열이름][길이정보] = {초기화 리스트};

// ex)
int arr1[7];				// 초기화 하지않고 배열만 선언
int arr2[5] = {1, 2, 3, 4, 5};		// 순차적으로 1, 2, 3, 4, 5로 초기화 됨
int arr3[] = {1, 2, 3, 4, 5, 6, 7};	// 초기화 리스트를 사용하면 배열의 길이를 생략할 수 있다.
int arr4[5] = {1, 2};			// 3, 4, 5번째 배열 요소는 0으로 채워진다.

// 변수를 통한 배열 길이 선언이 가능하지만 가급적 상수 사용을 권장!
int len = 20;
int arr[len];	// 가능

// 배열의 모든 요소를 0으로 초기화
int numArr[10] = {0};	// {0, }으로 해도 같음
  • 자료형 : 배열을 이루는 요소(변수)의 자료형
  • 배열 이름
  • 배열의 길이 : 배열의 길이를 선언할 때 변수를 이용할 수도 있다. 과거의 C표준에서는 배열의 길이 정보를 반드시 상수로 지정하도록 제한했기 때문에 범용적인 컴파일을 위해 가급적 배열 길이정보를 상수로 하는 것이 좋다.
  • 초기화 리스트(선택) : 기본 자료형 변수처럼 배열도 선언과 동시에 원하는 값으로 초기화할 수 있다. 배열 초기화를 목적으로 선언된, 중괄호 ``{ }``로 묶인 부분을 초기화 리스트라고 한다. 초기화 리스트를 사용하면 배열의 길이를 생략할 수 있으며, 배열의 길이보다 초기화 리스트의 요소 개수가 적을 경우 나머지는 @@0@@으로 채워진다.

      + 배열을 전역 변수로 초기화하지 않고 선언하면 전부 @@0@@으로 초기화된다.

 

 

 

 

배열 접근

// 배열 선언
int arr[3];

// 배열 접근
int arr[0] = 10;

// 반복문을 이용해서 배열의 모든 요소에 순차적으로 접근하는 것이 가능
for(int i = 0 ; i < 3 ; i++)
{
	printf("arr[%d] 입력 : ", i);
	scanf("%d", &arr[i]);		// 배열 요소를 대상으로 할 때는 변수와 마찬가지로 &연산자를 붙여야함
}

배열에 접근할 때 ``[ ]``연산자 사이에 배열의 위치정보를 명시하게 되는데 이를 인덱스(인덱스 값)라고 한다.

인덱스 값은 @@0@@부터 시작한다.

 

 

 

  + 배열 접근 시 주의사항 

#include <stdio.h>

int main(void)
{
	int arr[3];
	arr[0] = 1;
	arr[1] = 2;
	arr[2] = 3;
	arr[3] = 4;	// 오류여야 하지만 컴파일 에러가 일어나지 X
}

컴파일러는 배열 접근에 있어서 유효성 검사를 진행하지 않기 때문에 위와 같은 경우에도 컴파일 에러를 일으키지 않는다.

이 경우에는 할당하지 않은 메모리 공간을 침범하게 되므로 주의한다.

 

 

 

 

배열의 크기와 길이

#include <stdio.h>

int main(void)
{
	int arr1[5] = {1, 2, 3, 4, 5};	
	int arr2[] = {1, 2, 3, 4, 5, 6, 7};	// 초기화 리스트 사용시 배열길이 생략가능 
	int arr3[5] = {1, 2};
	
	printf("배열의 크기\n");
	printf("배열 arr1의 크기 : %d \n", sizeof(arr1));
	printf("배열 arr2의 크기 : %d \n", sizeof(arr2)); 
	printf("배열 arr3의 크기 : %d \n", sizeof(arr3)); 
	
	printf("\n배열의 길이\n");
	printf("배열 arr1의 길이 : %d \n", sizeof(arr1) / sizeof(int));
	printf("배열 arr2의 길이 : %d \n", sizeof(arr2) / sizeof(int));
	printf("배열 arr3의 길이 : %d \n", sizeof(arr3) / sizeof(int));
} 

배열의 이름으로 ``sizeof`` 연산자를 사용하면 byte단위의 배열 크기가 반환된다.

길이를 계산하고 싶으면 ``sizeof(arr1) / sizeof(int)`` 처럼 배열의 크기를 배열의 자료형으로 나눠야 한다.

 

실행 결과

 

 

  ■ 배열의 최대크기

  배열은 메모리의 스택에 생성되기 때문에 스택의 크기를 넘어서면 스택 오버플로우(stack overflow)가 발생하여 프로그램이 실행되지 않는다. 따라서 배열은 플랫폼에서 정한 스택의 크기보다 작게 선언해야하며 더 큰 크기를 사용하려면 ``malloc``함수로 동적 메모리를 할당해서 사용해야한다. (리눅스에서는 ##ulimit -a## 명령으로 스택 크기를 알아낼 수 있다.)

 

 

 

배열을 이용한 문자열 변수의 표현

char str[14] = "Good morning!";
char str[] = "Good";	// 배열 길이 생략 가능 !

  ``char``형 배열을 이용하면 문자열의 저장 및 변경이 가능해진다. `` " ``를 이용해서 문자열을 표현하며, 문자열을 선언과 동시에 지정하는 경우 배열의 길이를 생략하는 것도 가능하다.(컴파일러가 알아서 계산해서 배열 길이를 결정한다.) 문자열의 끝에는 @@\0@@이라는 특수문자(escape sequence)가 자동으로 삽입되어 실제 문자열의 길이보다 @@1@@더 길다. 따라서 문자열을 저장하는 목적으로 @@char@@형 배열을 선언할 경우 @@\0@@이 저장될 공간까지 고려해서 배열의 길이를 결정해야 한다. @@\0@@을 가리켜 널(null) 문자라고 하는데 문자열을 구분할 때 사용된다. 문자열에서 @@null@@문자는 매우 중요하며, @@null@@문자가 존재하지 않으면 컴파일러가 문자열로 인식하지 못한다. 

 

#include <stdio.h>

int main(void)
{
	char str[] = "Good morning!";
	printf("배열 str의 크기 : %d\n", sizeof(str));
	printf("널 문자 문자형 출력 : %c \n", str[13]);
	printf("널 문자 정수형 출력 : %d \n", str[13]);
	
	str[12] = '?';
	printf("문자열 출력 : %s", str);
	
	return 0;
} 

문자열을 변경하고, ``null``문자를 출력하는 예시이다.

 

실행 결과

 

 

#include <stdio.h>

int main(void)
{
	char str[50] = "I like C programming";
	printf("string : %s \n", str);
	
	str[8] = '\0';
	printf("string : %s \n", str);
	
	str[6] = '\0';
	printf("string : %s \n", str);
	
	str[1] = '\0';
	printf("string : %s \n", str);
	
	return 0;
} 

``printf`` 함수도 ``null``문자를 기준으로 문자열을 구분하기 때문에 ``%s``를 기반으로 문자열을 출력하면 ``null``문자의 위치에 따라 문장의 끝을 다르게 인식한다.

 

실행 결과

 

 

 

  ■ 공백과 null(\0)

#include <stdio.h>

int main(void)
{
	char nu = '\0';
	char sp = ' ';
	
	printf("null = %d \n", nu);
	printf("space = %d \n", sp);
	
	return 0;
} 

@@null@@ 문자(@@\0@@)의 아스키코드는 @@0@@, 공백의 아스키 코드는 @@32@@이므로 혼동하지 않도록 주의한다.

 

실행 결과

 

str[8] = '\0';
str[8] = 0;

``null``문자의 아스키 코드 값은 ``0``이므로 위의 두 문장은 같은 의미이다.

 

 

 

  ■ scanf 함수를 이용한 문자열의 입력

#include <stdio.h>

int main(void)
{
	char str[50];
	int idx = 0;
	
	printf("문자열 입력 : ");
	scanf("%s", str);	// 문자열을 입력받을 때는 &를 붙이지 X
	printf("입력 받은 문자열 : %s \n", str);
	
	printf("문자 단위 출력 : ");
	while(str[idx] != '\0'){
		printf("%c", str[idx]);
		idx++;
	}
	printf("\n");
	
	return 0;
} 

  C언어에서 표현하는 모든 문자열의 끝에는 @@null@@문자가 자동으로 삽입된다. 따라서 ``scanf`` 함수 호출을 통해 입력받는 문자열의 끝에도 ``null``문자가 삽입된다. @@scanf@@ 함수를 통해 문자열을 입력받을 때는 @@&@@연산자를 사용하지 않는다. 

 

실행 결과(공백이 없는 경우)

 

실행 결과(공백이 있는 경우)

  그러나 @@scanf@@ 함수는 공백을 기준으로 데이터를 구분 짓기 때문에 위와 같은 문장을 입력할 경우 ``He``, ``is``, ``my``, ``friend`` 총 4개의 문자열이 입력된 것으로 인식한다. 그래서 공백이 있는 문자열을 입력받을 때는 다른 함수를 사용해야 한다. 

 

 

 

 

 


다차원 배열

  2차원 이상의 배열을 다차원 배열이라고 한다. 논리적으로 2차원은 평면구조의 배열, 3차원은 직육면체 구조의 배열이다. C언어의 문법은 4차원 이상의 배열 선언을 문법적으로 허용하고 있지만 특정한 상황이 아니면 잘 사용하지 않는다. 2차원 배열도 메모리상에는 1차원 형태로 존재한다.

 

 

2차원 배열 선언 및 초기화

TYPE 배열이름[행의수][열의수];

// ex) 배열만 선언하는 경우
int arr1[3][4];
int arr2[2][6];

// ex) 선언과 동시에 초기화 하는 경우
int arr3[3][3] = {
		    {1, 2, 3},
                    {4, 5, 6},
                    {7, 8, 9}
                 };

int arr3[3][3] = {
		    {1},	// 비는 공간은 0으로 초기화됨
                    {4, 5},
                    {7, 8, 9}
                 };

// 한줄에 초기화 하는 것도 가능
int arr[3][3] = {1, 2, 3, 4, 5, 6, 7};

// 배열 크기를 생략할 경우 행의 길이만 생략가능
int arr1[][4] = {1, 2, 3, 4, 5, 6, 7, 8};
int arr2[][2] = {1, 2, 3, 4, 5, 6, 7, 8};

  2차원 배열은 1차원 배열과 달리 인덱스가 2개이다. 선언하면서 초기화하는 것도 가능한데, 한 줄로 선언해도 되고, 가독성 좋게 여러 줄에 해도 된다. 배열의 크기를 생략하는 경우는 배열의 행길이만 생략 가능하다.

 

 

 

 

2차원 배열 접근

  인덱스가 2개이므로 이중 ``for``문을 사용하면 2차원 배열에 순차적으로 접근하는 것이 가능하다.

 

 

  ■ 2차원 배열의 크기, 행, 열 길이

  2차원 배열 이름으로 sizeof 연산을 하게 되면 배열의 크기가 byte단위로 계산되어 나오므로(``int arr[3][4]``면 3 * 4 * 4 = 48byte) 행과 열의 길이를 각각 구해주어야 한다.

 

  •  행 길이  : sizeof(배열이름) / sizeof(배열의 행 한 줄)
    ex) sizeof(arr) / sizeof(arr[0]);
  •  열 길이  : sizeof(배열의 행 한 줄) / sizeof(자료형);
    ex) sizeof(arr[0]) / sizeof(int);

 

 

 

 

 


가변 길이 배열(Variable-Length Array, VLA)

자료형 *포인터이름 = malloc(sizeof(자료형) * 크기);

가변 길이 배열(프로그램 실행 중에 원하는 크기만큼 배열을 생성하는 기능)은 GCC에서는 지원하지만, Visual Studio 2015에서는 지원하지 않는다. 그래서 배열의 크기를 동적으로 지정하려면 포인터를 선언하고 메모리를 할당한 뒤 메모리를 배열처럼 사용해야 한다.

 

int numArr[10]	// int형 요소 10개를 가진 배열 선언
int *numPtr = malloc(sizeof(int) * 10);	// int 10개 크기만큼 메모리 할당

// 사용 방법은 같음
numArr[0] = 10;	// 배열을 인덱스로 접근하여 값 할당
numPtr[0] = 10;	// 포인터를 인덱스로 접근하여 값 할당

free(numPtr);	// 메모리 할당

배열과 메모리가 할당된 포인터는 생성방법만 다를뿐 값을 다루는 방법은 같다.

또한 포인터를 역참조한 값과 배열의 0번째 인덱스에 저장된 값은 같다.

 

 

  ■ 메모리를 할당해서 2차원 배열처럼 사용하기

  1. ``자료형 **포인터이름 = malloc(sizeof(자료형 *) * 세로크기)``로 세로 공간 메모리 할당
  2. 반복문으로 반복하면서 ``포인터[i] = malloc(sizeof(자료형) * 가로크기)``로 가로 공간 메모리 할당
  3. 반복문으로 반복하면서 ``free(포인터[i])``로 가로 공간 메모리 해제
  4. ``free(포인터)``로 세로 공간 메모리 해제

  메모리를 할당해서 2차원 배열처럼 사용하려면 위와 같은 과정을 거치면 된다. 메모리 할당 후에는 2차원 배열을 사용하던 것처럼 대괄호[ ]에 인덱스를 지정하여 값을 할당하거나 가져올 수 있다. 해제할때는 가로공간을 전부 해제한 후에 세로공간에 해당하는 메모리를 해제해야한다. (@@**@@포인터 하나만 @@free@@할 경우 가로공간에 할당되었던 메모리가 해제되지 않아 메모리 누수 발생)

 

 

 

'기타 > C' 카테고리의 다른 글

[C] 포인터와 배열, 함수  (0) 2020.05.07
[C] 포인터  (0) 2020.05.05
[C] 함수  (0) 2020.04.27
[C] 조건문, 반복문  (0) 2020.04.16
[C] printf, scanf 함수  (0) 2020.04.09

 

 

공부했던 자료 정리하는 용도입니다.

재배포, 수정하지 마세요.

 

 


 함수 

  • 함수의 정의
  • 함수의 유형
    * 여러 개의 return문이 존재하는 경우
  • 재귀 함수

 

 변수의 존재 기간과 접근 범위 

  • 지역 변수(Local Variable), 자동 변수(automatic variable)
  • 전역 변수
  • static변수
  • register 변수

 


 

함수

함수의 정의

[반환형] [함수이름] ([자료형 인자이름1], [자료형 인자이름2] ...)
{
    // 함수 호출시 실행될 문장
    return [리턴값] ;	// 반환값이 있는 경우
}

  한번 정의된 함수는 몇 번이고 호출이 가능하고, 함수 안에서 함수를 호출하는 것도 가능하다. (``main``을 포함한 모든 함수는 조건 및 상황에 관계없이 다른 함수를 호출할 수 있다.) 함수가 호출되면 호출된 함수의 영역으로 실행의 흐름이 이동하고, 호출된 함수가 반환되면 다시 함수를 호출했던 함수가 이어서 실행된다.

 

  • 반환형 : 함수 호출이 끝나고 반환될 리턴 값의 자료형
                반환 값이 없을 경우 ##void##라고 한다.
  • 함수 이름 : 함수의 이름 지정
  • 자료형 인자 이름 : 함수를 호출할 때 전달되는 인자의 자료형과 이름을 지정한다. 후에 함수를 호출할 때 정의된 인자 개수에 맞춰 매개변수를 넣어야 한다. 인자가 없는 경우 ##void##라고 하고 함수 호출 시 인자를 전달하면 안 된다.
  • return(선택) : 반환 값이 있을 때 사용한다.
    반환형이 ##void##인 함수에도 ##return##문을 사용할 수 있다. 사용하면 값을 반환하지 않고 함수를 빠져나간다는 의미!

 

 

 

함수의 유형

  함수는 인자와 반환 값의 유무에 따라 여러 가지 유형으로 나눌 수 있다. (인자의 수는 여러 개가 될 수 있지만 반환 값은 하나이므로 주의한다.)

 

   함수의 유형 

  1. 전달 인자 O, 반환 값 O
  2. 전달 인자 O, 반환 값 X
  3. 전달 인자 X, 반환 값 O
  4. 전달 인자 X, 반환 값 X

 

#include <stdio.h>
// 함수 정의
// 1, 전달인자 O, 반환값 O 
int Add(int num1, int num2)
{
	return num1 + num2;
}

// 2, 전달인자 O, 반환값 X
void ShowAddResult(int num)
{
	printf("덧셈결과 출력 : %d \n", num);
}

// 3, 전달인자 X, 반환값 O
int ReadNum(void)
{
	int num;
	scanf("%d", &num);
	return num;
}

// 4, 전달인자 X, 반환값 X
void HowToUseThisProg(void)
{
	printf("두 개의 정수를 입력하면 덧셈 결과가 출력됩니다. \n");
	printf("두 개의 정수 입력 : ");
}

// main 함수
int main(void)
{
	int result, num1, num2;
	HowToUseThisProg();
	num1 = ReadNum();
	num2 = ReadNum();
	result = Add(num1, num2);
	ShowAddResult(result);
	
	return 0;
 } 

4가지 유형의 함수를 선언하고 ``main``함수에서 호출하는 예시이다.

 

 

#include <stdio.h>

// 함수 선언
int Add(int num1, int num2);	//int Add(int, int);도 가능 
void ShowAddResult(int num);	//void ShowAddResult(int);도 가능 
int ReadNum(void);
void HowToUseThisProg(void);

// main 함수
int main(void)
{
	int result, num1, num2;
	HowToUseThisProg();
	num1 = ReadNum();
	num2 = ReadNum();
	result = Add(num1, num2);
	ShowAddResult(result);
	
	return 0;
 } 

// 함수 정의
int Add(int num1, int num2)
{
	return num1 + num2;
}

void ShowAddResult(int num)
{
	printf("덧셈결과 출력 : %d \n", num);
}

int ReadNum(void)
{
	int num;
	scanf("%d", &num);
	return num;
}

void HowToUseThisProg(void)
{
	printf("두 개의 정수를 입력하면 덧셈 결과가 출력됩니다. \n");
	printf("두 개의 정수 입력 : ");
}

  함수를 ``main``함수 아래에 정의하는 경우 ``main``함수 위에 함수 선언을 해놓지 않으면 컴파일 에러를 일으킨다. 함수의 선언에는 매개변수의 개수 및 자료형 정보만 포함되면 되기 때문에 ``int Increment(int)``같이 매개변수의 이름을 생략하고 선언하는 것도 가능하다.

 

 

  + 여러 개의 return문이 존재하는 경우

int MaxNumberCompare(int num1, int num2)
{
    if(num1 > num2) 
    	return num1;
    else
    	return num2;
}

  함수의 위치에 상관없이 ``return``문이 올 수 있고 ``return``문이 여러 개 오는 것도 가능하다. 그러나 위와 같이 ``return``문이 여러 개인 경우에도 최종적으로 함수를 빠져나가면서 반환되는 값은 하나뿐이다!

 

 

 

 

재귀 함수

#include <stdio.h>

int Factorial(int n)
{
	//재귀함수 탈출 조건
	if(n == 0)
	    return 1;
	else
	    return n * Factorial(n - 1);
}

int main(void)
{
	printf("1! = %d\n", Factorial(1));
	printf("2! = %d\n", Factorial(2));
	printf("3! = %d\n", Factorial(3));
	printf("4! = %d\n", Factorial(4));
	printf("9! = %d\n", Factorial(9));
}

 

실행 결과

  함수 내에서 자기 자신을 다시 호출하는 함수를 의미한다. 위의 예시 같은 경우 ``Factorial`` 함수를 실행하는 중간에 다시 ``Factorial`` 함수가 호출되면 함수의 복사본을 하나 더 만들어서 복사본을 실행하게 된다.(함수를 구성하는 명령문은 CPU로 복사되어 실행된다.) 그래서 함수가 완료되지 않은 상태에서 다시 호출하는 것이 가능한 것이다.

 

 

 

 

 


변수의 존재 기간과 접근 범위

   변수의 종류 

  • 지역 변수
  • 전역 변수
  • static 변수
  • register 변수

 

지역 변수(Local Variable), 자동 변수(automatic variable)

  • 지역변수의 지역은 중괄호 `` { } ``에 의해 형성되는 영역을 의미하고, 이 안에 선언되는 변수들을 모두 지역 변수라고한다.
  • 지역변수는 'stack' 메모리 영역에 할당된다.
  • 선언된 지역 내에서만 유효하고, 해당 지역을 벗어나면 자동으로 소멸된다.
  • 지역변수는 선언된 지역 내에서만 유효하기 때문에 선언된 지역이 다르면 이름이 같아도 문제가 되지 않는다.
  • 반복문이나 조건문에도 선언 가능하다.
  • 함수의 매개변수도 지역변수의 일종이다.
    선언문이 실행될 때 메모리 공간에 할당되었다가 선언문이 존재하는 함수가 반환되면 메모리에서 소멸된다.

 

 

 

 

전역 변수

  • 지역변수와 달리 중괄호 `` { } ``내에 선언하지 않는다.
  • 프로그램 전체 영역 어디서든 접근이 가능한 변수이다.
  • 프로그램 시작과 동시에 메모리 공간에 할당되어 종료 시까지 존재한다.
  • 별도의 값으로 초기화하지 않으면 @@0@@으로 초기화된다.
  • 많이 사용하면 프로그램이 복잡해지기 때문에 사용에 신중해야 한다.

 

 

 

static 변수

  • 지역변수에 ``static``을 붙여서 만든다.
  • 지역변수와 전역 변수의 특성을 동시에 가진다
    * 지역 변수 특성
    선언된 함수 내에서만 접근 가능

    * 전역 변수 특성
    처음 1회만 초기화되고 프로그램 종료 시까지 메모리 공간에 존재(전역 변수와 동일한 시기에 할당/소멸)
    초기화하지 않으면 전역 변수처럼 @@0@@으로 초기화

 

 

 

register 변수

  • 지역변수에 ``register``를 붙여서 만든다. (전역 변수에는 추가할 수 X)
  • 선언하면 해당 변수가 CPU 내의 레지스터에 저장될 확률이 높아진다.
    컴파일러는 이 선언을 힌트로 하여 레지스터의 활용 여부를 결정한다. 최종 결정은 컴파일러가 내리며, 반대로 아무 선언을 안 해도 컴파일러가 레지스터에 할당해야겠다고 판단하면 레지스터에 할당된다.

 

 

 

'기타 > C' 카테고리의 다른 글

[C] 포인터  (0) 2020.05.05
[C] 배열 (1차원 배열, 다차원 배열, 가변 길이 배열)  (1) 2020.04.28
[C] 조건문, 반복문  (0) 2020.04.16
[C] printf, scanf 함수  (0) 2020.04.09
[C] 자료형 변환  (0) 2020.04.08

+ Recent posts