선언(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에 할당한다
- malloc은 void* 형식의 포인터를 반환한다. 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);
'크래프톤 정글 > TIL' 카테고리의 다른 글
Red Black Tree의 개념과 삽입/삭제, C언어 구현 (0) | 2024.04.23 |
---|---|
[크래프톤 정글 5기] week04 C언어 주차 여섯번째 날, 이진 검색 트리, B-Tree (0) | 2024.04.16 |
[크래프톤 정글 5기] week03 알고리즘 주차 스물한번째 날, 프로시저, C / 어셈블리 코드 변환, callee-saved, 플래그 레지스터 (1) | 2024.04.10 |
[크래프톤 정글 5기] week03 알고리즘 주차 스무번째 날, 프로시저, 리턴주소, 함수호출, 디스어셈블 코드 실행추적 (1) | 2024.04.09 |
[크래프톤 정글 5기] 스택, 레지스터, 꼬리 재귀 최적화 (0) | 2024.04.09 |