Monday, June 21, 2010

C 언어

다음은 초 단기로 c 언어를 공부하고 정말 기억해야 하는 것만 추스린 것이다. 이는 사실 velvet를 분해해보고 싶은 단순한 호기심에 의한 것이다(velvet이 c 로 되어 있다). 다음은 "C 프로그래밍 윤상우 저 (주)프리렉"을 참고로 한다.

함수내 변수 사용시 반드시 변수 선언이 우선되어야 한다.

음수를 표현할때 2의 보수 체계를 기억해야 한다.


정수형과 실수형은 메모리의 비트를 숫자화하는 방식이 다르다.

정수형은 int를 실수형은 double를 사용하길 권장한다.

const 를 이용한 심볼릭 상수 선언시 초기화를 동시에 해주어야 한다. 이는 상수라는 속성상 변화할수 없기때문에 선언시 값을 할당해주어야 하는것이다.

함수는 호출되기 전에 정의되거나 선언되어야 한다.

문자열 변수를 서식문자 %d로 받으면 주소가 %s로 받으면 문자열이 된다. 사실 포인터가 가리키는 value를 지칭하기 위해서는 *을 사용하는데 그렇기 때문에 char * str = "good" 이라고 정의했을때 "good"을 보기위해서는 마치 *str을 불러야 할거 같지만 정작 서식문자만 %s로 해준다면 str를 사용하는 것이 맞다.

배열의 이름은 포인터이지만 변할수 없는 상수 포인터이다.

포인터는 데이터 타입에 상관없이 4바이트를 갖는다.

int a = 3, int *pa = &a 라고 하고 함수 func()이 있다고 할때 func(&a)의 경우 call-by-reference가 되지만 func(pa)는 call-by-value
가 되며 두 경우 모두 func의 입장에서 받는 변수는 int * temp (포인터) 의 꼴이 되어야 한다.

다차원 배열, 예를 들어 이차원 배열, int arr[2][4] 가 있을 때 이를 가르키는 포인터는 int (*pArr)[4] 와 같이 포인터가 가르키는 변수의 데이터 타입 뿐만 아니라 포인터 연산시 증감 되는 바이트의 갯수도 명시해주어야 한다.

배열 arr이 있을때, arr[i] == *(arr+i) 이나 이것이 주소인지 값인지는 상황에 따라 달라진다. arr 이 1차 배열일 경우에는 값이지만 arr이 다차배열일 경우에는 arr[i] 역시 주소를 값으로 갖으므로 이는 주소를 뜻한다.

함수를 구동하기 위해서는 함수를 RAM에 올리게 되고 함수 이름은 함수의 위치를 나타내는 포인터가 된다. 포인터라함은 변수 이므로 이를 가르키는 함수 포인터를 만들 수 있으면 그 타입 선언은다음과 같다. 예를 들어 void func(char * str) 이라는 함수가 있을때 이를 가르키는 함수 포인터 정의는 void (*fPtr) (char*) = func 와 같다.

문자열 입력함수로 gets와 fgets 가 있지만 gets는 할당된 배열의 크기보다 큰 길이의 문자열이 입력 되었을때 overflow를 일으키므로 fgets 을 사용하는 것이 좋다.

필요한 함수를 찾아보고 싶은땐 C reference를 참고하자.

구조체란 하나 이상의 변수를 그룹 지어서 '사용자 정의 자료형'을 정의하는것이다. 구조체 멤버에 문자열 배열이 존재할 시에 그 변수에 문자열을 초기화 할시에는 strcpy함수를 이용해야 한다. 예를 들어 구조체 변수 person이 char name[10]을 멤버로 갖고 있을때 person.name = "김세환" 식으로 초기화를 하면 에러가 생기는데 이는 앞에서 언급한 내용(배열 이름은 상수 포인터이다)에 위배되는 문법이기 때문이다.

*연산자는 . 연산자에 비해 우선순위가 낮기 때문에 구조체 변수를 가르키는 포인터를 이용하기 위하선 (*pMan).name 식으로 괄호를 이용해야 한다. 혹은 pMan -> name 도 가능하다.

typeof 키워드를 이용하여 기본자료형에 사용자가 원하는 이름을 붙일 수도 있으며 이를 이용하여 구조체에 새로운 이름을 정의하여 구조체 변수 선언시 struct 를 넣지 않아도 되도록 할수 있다.

메모리(RAM)은 크게 데이터영역, 힙 영역, 스택 영역으로 나뉘며, 데이터 영역은 전역변수, static 변수가 스택영역은 지역변수, 매개 변수가 힙영역은 프로그래머가 할당한 변수가 자리잡게 된다. 변수의 특징에서 알수 있듯이 데이터 영역은 프로그램 시작과 동시에 변수가 메모리 할당되며 프로그램이 끝나야 메모리에서 지워진다. 반면에 스택영역의 변수는 함수가 호출될때 메모리를 할당되며 해당 함수가 끝나면 메모리에서 지워지며 힙영역의 변수는 프로그래머가 malloc(memory allocation의 약자) 를 통해 메모리를 할당하고 free 함수를 통해 메모리에서 지우게 된다.

스택 영역과 데이터 영역에 할당될 메모리의 크기는 컴파일되는 동안에 결정되어야 한다. 이것이 배열의 크기를 정할때 반드시 상수를 이용해야 하는 이유이다.

malloc에 의해 힙영역에 메모리를 잡을때 메모리가 부족하면 NULL을 리턴한다. 즉, 항상 malloc으로 힙 영역에 메모리를 지정하면 메모리가 여유가 있지 않을수 있음을 확인해야 한다. 또한 힙 영역에 저장된 변수는 반드시 포인터를 통해서만 접근 가능하다.

컴파일을 더 세분화시키면 전처리(preprocess)와 컴파일로 나뉜다. #로 시작하는 문장은 전처리기 지시자라고 하며 #define은 단순 치환 작업을 요청할 때 사용되는 지시자이다. 예를 들어 #define PI 3.1415라고 하면 소스에서 PI라고 되어있는 것을 3.1415로 전처리 과정에서 치환하라는 뜻이며 PI를 매크로 3.1415를 대체 리스트라고 하며 #define 지시자를 통해 매크로 함수도 선언 할수 있다.

#if, #elif, #else, #endif 는 전처리기에게 조건을 제시한다. 곧 컴파일러에 의해 컴파일되기 전에 전처리기가 위의 조건문을 보고 코드에 소스를 넣는다.
#ifndef, #endif는 헤어파일이 중복 포함되어 있음을 방지 하고자 하는것으로 #ifndef (if not defined) 는 #define이 전처리 지시자가 따라야 한다. 곧 #define에 의해 특정 변수가 정의되어 있는가로 #ifndef로 확인하고 정의되어 있지 않으면 #ifndef, #endif  안에 구문을 넣게된다. #if defined() || defined() 등에 여러개를 확인하기 위해서는 if defined를 사용해야 한다.