크래프톤 정글/TIL

[크래프톤 정글 5기] week04 C언어 주차 두번째 날, C언어 문법, 포인터

양선규 2024. 4. 12. 22:12
728x90
반응형

선언(declaration)과 정의(definition)

선언(declaration)

- 컴파일러가 참조할 식별자(identifier)의 이름을 알린다.

- 식별자란 변수의 타입, 함수의 인자목록을 뜻하며 이름은 변수, 함수, 클래스의 이름 등을 뜻한다.

- 선언은 메모리 영역 상에 올리지 않기 때문에 중복되어도 문제가 되지 않는다.

 

extern int a; // 전역변수 선언

int add(int a, int b); // 함수 선언

class ClassId; // 클래스의 선언

 

 

정의(definition)

- 식별자와 이름으로부터 코드를 생성한 것

- 정의는 고유해야 한다. 같은 식별자와 이름의 정의가 2개 이상이면 컴파일 에러 발생

 

int add(int a, int b)

{ // 함수의 정의 (함수 본체가 있다)

return a+b;

}

 

struct C // 구조체의 정의

{

int a;

int b;

};

 

class D // 클래스의 정의

{

int a;

int b;

};

 

 

전방선언(Forward Declaration)

- 식별자를 정의하기 전 식별자의 존재를 컴파일러에 미리 알리는 것

- 필요에 따라 함수, 변수, 클래스 등을 전방선언한다.

- 컴파일 시간을 단축시키며, 헤더포함 의존성을 줄여준다.

ex) 호출자 함수가 피호출자 함수보다 먼저 정의된 경우

 

// 예제 코드

void foo(int);    // 함수 foo 전방선언

 

void bar() {    // 함수 bar 정의, 정의되지 않은 foo 사용

foo(42);

}

 

void foo(int x) {    // foo 정의

printf("Value of x: %d\n, x);

}

// 이렇게 정의되기 전에 미리 선언하여 컴파일러가 에러를 일으키지 않도록 미리 선언만 해두고 나중에 정의할 수 있다.

 

 

static, extern

static

- 전역 변수에 사용되는 경우 : 해당 변수를 다른 소스 파일에서 접근할 수 없게 한다.

- 지역 변수에 사용되는 경우 : 해당 변수의 수명이, 변수가 정의된 블록 내로 제한된다.

- 함수에 사용되는 경우 : 해당 함수를 다른 소스 파일에서 접근할 수 없게 한다.

ex) static int a = 5;

 

extern

- 전역 변수/함수에 사용되는 경우, 해당 변수/함수가 다른 소스 파일에서 선언되어서 가져온 것을 알린다.

- 반드시 extern을 써야 다른 소스 파일에서 가져온 것을 쓸 수 있는 것은 아니나, 컴파일러/링커가 소스 파일을 올바르게 링크할 수 있도록 돕는다.

- 명시적, 관례적인 이유도 있다.

ex) extern int a;

 

 

enum, union

enum(열거형)

- 연속된 정수값들을 가지는 상수 집합을 정의하는 데 사용된다.

- 각 상수는 고유한 이름으로 식별되고, 순서대로 0부터 시작하여 1씩 증가하는 값을 갖는다.

ex)

enum Days {

MONDAY,

TUESDAY,

WEDNESDAY,

THURSDAY,

FRIDAY,

SATURDAY,

SUNDAY

};

 

union(공용체)

- 메모리를 여러 가지 방식으로 해석할 수 있는 자료형

- 다양한 자료형의 멤버를 하나의 메모리 공간에 저장할 수 있는데, 모든 멤버는 동일한 메모리 위치를 공유한다.

- 따라서, 한 번에 하나의 멤버만을 가질 수 있다.

- 쉽게 말해서, 하나의 변수로 여러 개의 자료형을 돌려가며 쓸 수 있다.

 

ex)

union Data { // 공용체 정의

int i;

float f;

char str[20];

};

 

int main() {

union Data data; // data 라는 이름으로 공용체 사용

data.i = 10;

printf("data.i: %d\n", data.i);

 

data.f = 3.14; // 정의했던 자료형을 모두 사용 가능하다

printf("data.f: %f\n", data.f);

 

strcpy(data.str, "Hello");

printf("data.str: %s\n", data.str);

 

return 0;

}

 

 

malloc, free

malloc()

- <stdlib.h> 헤더 파일에 선언되어 있음

- 프로그램 실행 중에 필요한 만큼의 메모리 동적 할당 가능

- 주어진 인수의 바이트 크기만큼 메모리에서 연속된 공간을 할당 후, 그 시작 주소를 반환한다.

 

ex)

int *pi;     // 포인터 변수 선언

pi = (int *)malloc(sizeof(int));      // 메모리 동적 할당하여 할당한 주소를 pi에 할당한다

- mallocvoid* 형식의 포인터를 반환한다. void는 모든 유형의 데이터를 가리킬 수 있는 범용 포인터이다.

- 따라서 할당된 메모리를 특정 데이터 형식으로 다루기 위해 포인터를 "캐스팅" 해야한다.

(int *) : 반환되는 포인터를 int형식으로 캐스팅한다

-> 캐스팅 연산자가 반환되는 포인터 변수의 데이터 형식과 일치해야 한다.

 

 

free(cur)    // (cur은 포인터변수)

- 해당 변수가 갖고 있던 메모리공간을 할당 해제한다.

- 공간을 할당 해제하지만, 메모리 주소 자체는 변수가 계속 가지고 있는다.

- 할당 해제된 후에 *cur 등을 통해 해제된 메모리에 접근하면 안된다. 예기치 않은 오류가 발생할 수 있다.

- free 이후 다시 해당 변수를 사용하려면 다시 메모리를 할당하거나 초기화 해야 한다.

- 할당 해제된 포인터 변수는 “dangling pointer” 라고 하며, 이 주소에 다시 접근하는 걸 방지하기 위해서, 관례적으로 해당 포인터 변수를 NULL로 설정해 주어야 한다.

 

 

포인터와 연산자 *, &

- 연산자 *는 포인터 변수를 선언하거나, 포인터 변수의 값을 출력(의미)할 때 사용한다.

    -> int *a;

- 연산자 &는 특정 변수에 할당된 메모리 주소를 의미한다.

    -> &x    // 변수 x에 할당된 메모리 주소

 

ex)

int *a;     // 포인터 변수 선언, *를 붙여 포인터 변수라는 것을 알린다.

int x = 5;     // x선언과 동시에 5로 초기화

a = &x;     // 변수 x의 주소를 포인터 변수 a에 할당한다.

 

이렇게 되었을 때, *a는 5를 의미한다.

a는 x의 메모리 주소를 의미한다.

,   *a x와 같고 / a &x와 같다.

 

 

 

 

이제 아래 코드를 이해해보자.

 

이해가 필요한 코드

 

ptr은 2중 포인터이며, 0x00000000FFFF8392가 저장되어 있다. 편의상 8392라고 하겠다.

name은 char형 포인터 변수이며, char로 형변환된 *ptr의 값이 들어가 있다.

마지막으로 %s 포맷팅을 이용해 name의 값을 출력한다.

 

풀이과정

 

void** ptr = 0x00000000FFFF8392;

이것은 ptr 포인터 변수에 8392 주소가 저장되었음을 의미한다.

ptr은 2중 포인터이니, *ptr은 8392주소의 value를 의미할 것이고

**ptr은 8392주소의 value주소의 value를 의미할 것이다.

애초에 **ptr이 2중 포인터이니, 8392주소의 value도 메모리 주소 형태일 것이다.

 

char* name = (char*) *ptr;

ptr이 가리키는 첫 번째 주소의 value를(주소 형태일 것이다), char형태로 형변환하여 name 포인터 변수에 할당한다.

8392주소의 value는 "A" 라고 가정, name에 "A"가 담겼다.

 

printf("%s", name);

name을 %s포맷팅으로 출력하는 코드다. name에 "A"가 저장되었으니 "A"라는 메모리 주소의 value를 출력해야 한다. 하지만 여기서 %s의 특수성이 드러난다. %s는 변수를 마치 배열처럼 인식한다. string 자료형이 배열과 유사해서 그런 것 같은데, 결과적으로 "A"라는 주소를 시작으로 연속된 데이터가 전부 출력되게 된다.

즉 메모리의 "A"주소에 "H"가 있었고 연속해서 e, l, l, o 가 있었다고 한다면 %s에 의해 Hello가 출력되게 된다.

 

하지만 이건 %s의 특수성 때문에 그런 것이고, 만약 값이 정수였거나 문자열 1글자만 출력을 하는 조건이었다면 name 앞에 *를 붙여 주어야 한다.

ex)

printf("%d", *name); 

printf("%c", *name);

 

 

728x90
반응형