함수 고급 이론 — 함수 포인터, 콜백, Lookup 테이블
·
Language/C
함수도 메모리 어딘가에 올라가 있고, 그래서 주소를 가진다. 그 주소를 변수에 담을 수 있다면, 어떤 함수를 호출할지를 실행 중에 바꿀 수 있다. 이 발상에서 함수 포인터, 콜백, 그리고 함수 포인터 배열을 이용한 빠른 분기가 나온다.함수 포인터함수를 부를 때 쓰는 ()는 함수 호출 연산자다. 이 연산자의 피연산자는 괄호 왼쪽에 오며, 반드시 함수형이어야 한다. 그리고 함수 이름은 배열 이름과 마찬가지로 주소 상수다. 배열 이름이 첫 원소의 주소이듯, 함수 이름은 그 함수 코드가 올라간 위치(프로시저 주소)를 가리킨다. 그 주소를 담는 변수가 함수 포인터다. 함수의 이름(상수)을 저장할 수 있는 포인터이고, 그 자체가 함수 호출 연산자의 피연산자가 될 수 있다.#include int GetMax(int ..
전처리기 — #include, 매크로, 조건부 컴파일
·
Language/C
컴파일이 시작되기 전에 한 단계가 먼저 지나간다. 소스 코드를 훑으며 헤더를 합치고, 이름을 값으로 바꾸고, 조건에 따라 코드를 넣거나 빼는 작업이다. 이 선행 처리를 맡는 것이 전처리기다. 이번 글에서는 #include, #define과 매크로, 특수화 연산자, 그리고 조건부 컴파일을 다룬다.전처리기와 #include전처리기(preprocessor)는 컴파일 전에 선행 처리를 수행하는 문법이다. 모든 전처리기 지시문은 # 기호로 시작한다. 하는 일은 크게 네 가지다. 헤더 포함, 심볼릭 상수 정의, 매크로 정의, 그리고 조건부 컴파일이다. 핵심은 "컴파일 전에 소스 코드 자체를 바꾼다"는 점이다. 전처리기가 손본 결과물이 컴파일러로 넘어간다. 가장 익숙한 전처리기가 #include다. 헤더 파일을 소스..
변수와 상수 고급 — const, 심볼릭 상수, 그리고 컴파일러 최적화
·
Language/C
변수와 상수를 더 깊이 다룬다. 값을 못 바꾸게 막는 const, 최적화를 막는 volatile, 이름 있는 상수를 만드는 여러 방법, 그리고 여러 파일에 걸친 변수 공유까지. 이 문법들은 공통적으로 "더 읽기 좋고 더 최적화하기 좋은 코드"를 향한다.형한정어와 const형한정어(type qualifier)는 변수 선언에 붙여 그 성질을 한정하는 문법이다. const, volatile, extern, typedef가 여기 속하고, 모두 선언과 관련되며 컴파일러 최적화에 깊이 연관된다. 그중 가장 많이 쓰는 것이 const다. const는 변수가 할당받은 메모리의 값을 바꿀 수 없도록 한다. 즉 변수를 상수화해 읽기 전용으로 만든다.#include int main(void){ const int nCU..
C언어 파일입출력
·
Language/C
프로그램이 다루는 데이터는 보통 RAM에 있다가 프로그램이 끝나면 사라진다. 데이터를 영구히 보관하려면 SSD나 HDD 같은 2차 메모리에 파일로 저장해야 한다. 이번 글에서는 파일 시스템이 무엇인지부터 시작해, 파일이 어떻게 추상화되는지, 스트림과 텍스트/바이너리의 차이, 그리고 실제 파일 입출력 함수와 버퍼링까지 다룬다.파일 시스템과 파일2차 메모리(SSD, HDD)를 관리하는 체계가 파일 시스템이다. 윈도우의 파일 시스템인 NTFS는 논리 드라이브 단위와 경로로 파일을 관리한다. 경로에서 .은 현재 디렉토리, ..은 상위 디렉토리를 뜻하고, 경로를 적는 방식에 따라 절대 경로와 상대 경로로 나뉜다. 파일에 대해 가장 중요한 관점은 "누가 파일에 접근하는가"다. 파일에 접근하는 주체는 프로세스다. 프..
구조체와 공용체
·
Language/C
배열이 같은 자료형을 모은 것이라면, 구조체는 서로 다른 자료형을 하나로 묶어 새로운 형식을 만드는 문법이다. 이번 글에서는 구조체의 선언과 사용, 동적 할당, 함수와의 관계, 구조체를 멤버로 갖는 구조체와 연결 리스트, 그리고 비트필드·공용체·멤버 맞춤까지 다룬다.구조체 선언과 정의구조체는 여러 자료형을 모아 하나의 새로운 형식으로 기술하는 사용자 정의 형식이다. 배열이 같은 것들의 모임이고 인덱스로 접근한다면, 구조체는 서로 다른 것들의 모임이고 인덱스 대신 이름(멤버)으로 접근한다.#include #include struct USERDATA{ int nAge; char szName[32]; char szPhone[32];};int main(void){ struct USERDAT..
함수 응용 (2) — atoi, time, rand, system
·
Language/C
이번에는 표준 라이브러리가 제공하는 유틸리티 함수들을 정리한다. 문자열을 숫자로 바꾸는 변환 함수, 시간을 다루는 함수, 난수, 그리고 외부 명령 실행과 프로그램 종료 함수다. 각각 짧지만 자주 쓰이고, 알고 쓰지 않으면 함정에 빠지기 쉬운 것들이다.문자열을 숫자로 — atoi, atof문자열로 들어온 숫자를 실제 숫자 타입으로 바꿀 때 변환 함수를 쓴다. atoi는 정수로, atol은 long으로, atof는 실수로 변환한다.#include #include int main(void){ char szBuffer[32]; int nResult = 0; printf("Input string: "); gets_s(szBuffer, sizeof(szBuffer)); nResult = ..
함수 응용 (1) — call by reference와 Stack frame
·
Language/C
앞의 포인터 시리즈에서 메모리 구조와 주소, 동적 할당을 다뤘다. 이번에는 그 개념들을 함수에 적용한다. 함수에 값을 어떻게 넘기는지(call by value/reference), 지역변수의 수명과 Stack frame, 재귀 호출, 그리고 문자열 처리 함수와 버퍼 오버런까지 정리한다.매개변수 전달의 기본함수를 부르는 쪽을 Caller, 불리는 쪽을 Callee라고 한다. Caller가 Callee를 호출할 때 매개변수에 넘길 구체적인 값(실인수)을 적어주고, Callee가 값을 반환할 때까지 기다린다. Callee 입장에서 매개변수는 함수 안에서 지역변수처럼 쓰인다.매개변수가 어디에 담겨 전달되는지는 환경에 따라 다르다. 32bit에서는 Stack 메모리를 거쳐 전달되지만, 64bit에서는 CPU의 레..
메모리와 포인터 (3) — 문자열, 다차원 포인터, 정적 메모리
·
Language/C
앞의 두 편(1편, 2편)에서 메모리 구조, 포인터와 배열, 동적 할당까지 다뤘다. 이번에는 문자열을 다루는 표준 함수들, 할당된 메모리의 크기를 바꾸는 realloc(), 포인터를 여러 단계로 겹치는 다중 포인터, 다차원 배열을 가리키는 포인터, 그리고 함수가 끝나도 사라지지 않는 정적 메모리와 기억부류 지정자를 정리한다.문자열 복사 — strcpy_s문자열을 다른 곳으로 옮길 때 단순 대입을 쓰면 의도와 다른 일이 벌어진다.#include #include #include int main(void){ char szBuffer[] = "Hello"; char* pszBuffer = szBuffer; char* pszHeap = malloc(16); strcpy_s(pszHeap, 1..
메모리와 포인터 (2) — 포인터와 배열, 동적 할당
·
Language/C
앞 글에서 포인터가 주소를 담는 변수라는 것을 봤다. 이 글에서는 그 포인터로 배열을 다루는 법, 그리고 실행 중에 메모리를 직접 할당받아 쓰는 동적 할당을 본다.포인터와 1차원 배열배열 이름은 0번 요소의 주소다(배열 글에서 본 내용이다). 그래서 배열을 다룰 때는 그 요소의 형식을 가리키는 포인터를 선언해 쓰는 것이 일반적이다. int 배열은 int*로, char 배열은 char*로 관리한다.#include int main(void){ int aList[5] = { 0 }; int* pnData = aList; // 배열 이름 = 0번 요소 주소 // int* pnData = &aList[0]; // 위와 같은 의미 printf("aList[0] : %d\n", aL..