1. 1. C언어 Compile 과정
Compile이란 쉽게 말해 소스 코드를 컴퓨터가 실행할 수 있는 형태로 변환하는 과정을 의미하며 아래 4가지 단계를 거쳐 사용자
설정한대로 원하는 형태의 결과물을 얻을 수 있다.(e.g. exe, out)

1) Prepreocessor 과정 소스파일(.c) -> 전처리파일(.i)
- 매크로 처리: #define을 통해 정의한 메크로를 값으로 치환
- 헤더파일 처리: #include 지시문을 처리, 헤더 파일의 내용을 해당 소스 코드에 삽입
- 조건부 처리: #ifdef(#define 여부), #if(참 여부), #elif 등의 조건부 컴파일 지시문을 처리
- 주석 제거
- 여러 소스 코드 파일을 하나의 파일로 합병(헤더 파일은 중복 방지로 합병 x)
<cpp />
#include <stdio.h> // 지시문 처리, stdio.h 내부 코드가 복사됨 e.g. int printf(const char *format, ...);
#define PI 3.14
int main() {
#if defined(1) // 조건부 처리
double circle = PI * 2; // 3.14*2, 매크로 처리
#else
printf("");
#endif
return 0;
}
2) 컴파일러 (Compiler) – 전처리 파일(.i) → 어셈블리 코드(.s)
- 어휘 분석, 구문 분석, 의미 분석을 수행하여 각종 문법 오류를 체크하여 CPU가 해석할 수 있는 어셈블리 코드로 변환
- 최적화 수행: 불필요한 연산 제거 및 실행 속도를 높이기 위해 코드 실행 순서를 변경
- 전역 변수와 정적 변수 등에 필요한 메모리 공간 할당 (.data, .bss 섹션).
3) 어셈블러 (Assembler) – 어셈블리 코드(.s) → 목적 파일(.o/.obj)
- 어셈블리 코드를 기계어(0 1 0 1) 로 변환 후 목적 파일 (.o 또는 .obj) 생성
- 각 함수와 변수(심볼)의 정보를 포함하는 심볼 테이블 생성.
4) 링커 (Linker) – 목적 파일(.o) → 실행 파일(.exe/.out)
- 여러 개의 .o (또는 .obj) 파일을 묶어 하나의 실행 파일을 생성(.exe, out)
- 각 심볼(함수, 변수)에 대해 실제 메모리 주소 할당
- 프로그램에서 사용하는 라이브러리(.lib, .dll, .so)와 연결.
5) 로더 (Loader) – 실행 파일(.exe) → 실행(프로세스 생성)
- 생성된 실행 파일을 메모리에 로딩하여 프로세스(Process)를 생성 후 프로그램 실행 시작
- 운영체제(OS)가 실행 파일을 적절한 프로세스로 변환하여 CPU에서 실행 가능하게 만듦.
6) 빌드 (Build) – Compile(컴파일) + Generate(생성) + 추가 작업
컴파일 과정 + 각종 파일 생성 과정 + 추가적인 여러 작업을 포함한 전체 과정이며
소스 코드만 컴파일하는 것이 아니라 리소스 파일, 설정 파일, 패키징, 최적화 등 추가 작업이 필요할 때 Build를 수행한다.
2. 2. C언어 구성
C언어를 구성하는 요소들이다. 절차 지향 언어이기 때문에 Class 등의 개념은 없다.
개념 | 설명 | 예시 |
식별자 | 변수, 함수 등에 부여하는 이름. 알파벳, 숫자, _만 가능 | int age;, void func(); |
변수 | 데이터를 저장하는 메모리 공간. 선언 후 사용해야 함. | int a = 10;, char ch = 'A'; |
자료형 | 변수에 저장될 값의 형태를 결정하는 키워드 | int, float, char, double |
예약어 | C언어에서 미리 정의된 키워드. 변수 이름으로 사용 불가 | int, return, if, else |
문자열 | 마지막에 \0이 포함된 문자 배열. char*도 사용 가능 | "Hello", char str[10] |
Escape Sequence | \로 시작하는 특수 문자 코드 | \n (줄바꿈), \t (탭) |
아스키 코드 | 문자를 숫자로 변환하여 처리하는 1바이트(8bit) 문자 표현 방식 | 'A' → 65, '0' → 48 |
구조체 | 여러 데이터를 하나의 타입으로 묶는 사용자 정의 자료형 | struct Person { char name[20]; int age;}; |
공용체 | 구조체와 유사하지만, 모든 멤버가 같은 메모리 공간을 공유 | union Data { int i; float f; }; |
함수 |
특정 작업을 수행하는 코드 블록 | int sum(int a, int b) { return a + b; } |
3. 3. 데이터 Type
구 분 | 자료형 | 범위 | 바이트 |
문자형 | char | -128 ~ 127 (-2^7 ~ 2^7-1) | 1 |
unsigned char | 0 ~ 255 (0 ~ 2^8-1) | 1 | |
short | -32768 ~ 32767 (-2^15 ~ 2^15-1) | 2 | |
unsigned short | 0 ~ 65535 (0 ~ 2^16-1) | 2 | |
정수형 | int | -2147483648 ~ 2147483647 (-2^31 ~ 2^31-1) | 4 |
unsigned int | 0 ~ 4294967295 (0 ~ 2^32-1) | 4 | |
long | -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 (-2^63 ~ 2^63-1) | 8 | |
unsigned long | 0 ~ 18,446,744,073,709,551,615 (0 ~ 2^64-1) | 8 | |
실수형 | float | ±3.4 * 10^-38 ~ ±3.4 * 10^38 (유효자릿수 7) | 4 |
double | ±1.7 * 10^-308 ~ ±1.7 * 10^308 (유효자릿수 15) | 8 | |
long double | ±3.4 * 10^-4932 ~ ±1.1 * 10^4932 (유효자릿수 19) | 10 | |
무자형 | void |
- 컴퓨터는 비트 단위의 이진수로 모든 데이터를 처리 하며 한번에 처리하는 비트수는 최근에는 64비트가 가장 흔하다.
- 음수는 모든 비트를 반전 시킨 후 (0->1, 1->0) 1을 더해 음수로 변환한다. (2의 보수 음수 표기법)
- usigned 이면 최상위 bit에 음수 또는 양수를 표기할 필요 없어 더 큰 양수 표현이 가능하다.
1) 부동 소수점 표준 표현 방법
컴퓨터에서 소수를 표현하는 방법으로 부호, 지수, 가수로 나누어 비트에 저장한다.
32 비트의 float 기준으로 최상의 1비트는 부호(s), 하위 8비트는 지수(e), 하위 23비트는 가수(m)을 저장한다.
10.25f(1010.01)을 부동 소수점 방식으로 변환하면
- 소수점을 왼쪽으로 이동시켜 정수부가 한자리가 되도록 한다. → 1.01001
- 이동시킨 자릿수 만큼 2의 지수로 곱해준다. → 1.01001 x 2^3
- s = 0, m = 1.01001, e = 3이 된다.
- 가수를 23bit가 되도록 나머지 비트를 0으로 모두 채운다.
- 지수를 bit에 저장할 때 3 + 127 -> 10000010로 저장한다.
4. 4. 표준 입/출력 함수
입력 및 출력을 위해 사용하는 함수이다.
- getchar() -> 한글자 출력, chat ch = getchar();
- putchar() -> 한글자 입력, putchar('ch');
- gets() -> 문자열 입력, char buffer[100]; gets(buffer);
- puts() -> 문자열 출력, puts(buffer);
- scanf() -> 문자의 값을 입력, scanf("%d, &a);
- printf() -> 문자의 값을 출력, printf("%d", a);
변수의 주소 x 값만 copy해 전달됨(연산을 넣어도 실제 값을 변하지 않음)
자료형 | 설명 | 형식 지정자 | 바이트 크기 |
char | 문자형 | %c | 1 |
string | 문자열 | %s | 문자 개수+NULL 만큼 |
unsigned char | 부호 없는 문자형 | %c | 1 |
short | 짧은 정수형 | %hd | 2 |
unsigned short | 부호 없는 짧은 정수형 | %hu | 2 |
int | 정수형 | %d | 4 |
unsigned int | 부호 없는 정수형 | %u | 4 |
long | 긴 정수형 | %ld | 4 or 8 |
unsigned long | 부호 없는 긴 정수형 | %lu | 4 or 8 |
long long | 아주 긴 정수형 | %lld | 8 |
float | 단정도 부동 소수점 숫자 | %f 또는 %e | 4 |
double | 배정도 부동 소수점 숫자 | %lf 또는 %le | 8 |
void * | 포인터 | %p | 4 or 8 |
입력을 10으로 주고 엔터를 치면 10과 \n가 들어간다. scanf는 10만 읽어가고 엔터는 안읽는다.
따라서 scanf 후 fflush(stdin)으로 버퍼를 비워주거나 getchar(); 로 입력 받아 \n을 입력받지 않게 한다.
5. 5. 연산자
1) 산술 연산자순위 | 유형 | 연산자 | 결합성 |
1 | primary | () [] . -> | → |
2 | unary | & * + - ! ++ -- (형변환) sizeof | ← |
3 | binary | * / % | → |
4 | binary | + - | → |
5 | shift | << >> | → |
6 | 관계 | < <= > >= | → |
7 | 동등 | == != | → |
8 | 비트곱 | & | → |
9 | 비트반전 | ^ | → |
10 | 비트합 | → | |
11 | 논리곱 | && | → |
12 | 논리합 | → | |
13 | 삼항 | ← | ← |
14 | 대입 | = += -= *= /= %= <<= >>= &= ^= | ← |
15 | 순서 | , | → |
2) 관계 연산자
관계 연산자는 C에서 0 < i < 20 이렇게 절대 쓰면 안된다. 0 < i -> True, i < 20 -> True 이렇게 계산함..
연산자 | 설명 | 예시 |
< | 작다 | if(a < b) |
<= | 작거나 같다 | if(a <= b) |
> | 크다 | if(a > b) |
>= | 크거나 같다 | if(a >= b) |
== | 같다 | if(a == b) |
!= | 같지 않다 | if(a != b) |
3) 논리 연산자
연산자 | 설명 | 예시 |
! | 부정 | if(!a) |
&& | 논리곱 | if(a == b && c == d) |
|| | 논리합 | if(a == b || c == d) |
4) 대입 연산자
단순 대입 연산자 | = |
복합 대입 연산자 | +=, -=, *=, /=, %=, &=, ^=, |=, <<=, >>= |
5) 증가/감소 연산자
6) 비트 연산자
<<, >> 는 shift 연산자이며 2로 나누거나 곱하는 효과가 있다. << 1: 2^1, << 2: 2^2
7) ?, 삼항 연산자
조건식 ? 참일 때 값 : 거짓일 때 값
k % 3 == 0 ? 5 : 10; k를 3으로 나눈 나머지가 0일 경우 5를 반환 아니면 10을 반환
8) sizeof 연산자
sizeof 연산자는 type과 변수의 크기를 알 수 있다. sizeof(double) -> 8, type일때는 괄호가 필수
sizeof 안의 식은 실제로 수행되지 않는다.
9) 형변한 연산자
float a = 2.4
printf("%d", (int)a);
10) 콤마 연산자
int a ,b;
a = (b = 0, b + 2); // b에 0을 대입 후 +2를 더함 열거한 순서대로 수행
11) !, 부정 연산자
int a = 5;
printf("a값은 %s\n", !(a<6) ? "true" : "false");
해당 조건의 true, false 값을 반전시킴
6. 6. 문법
조건식의 참/거짓 여부에 따라 분기 처리 조건이 제한적이지 않은 경우에 쓰임
<cpp />
if (conditon)
statement1 -> if문 안에 하나만 있으면 가로 안쳐도됨 (2개 부터는 밖으로 인식)
2) Switch case문
특정 값과 일치 여부에 따라 분기 처리 조건이 제한적이고 명확한 경우에 쓰임
보통 조건값이 4개 이상일 경우 부터 메모리 절약 효과가 있으며 가독성이 좋다.
break는 원할 때 걸면 되며 default(예외) 상황을 정의하는 것이 좋다.
<cpp />
switch(a)
{
case a : 문장;
break;
default: printf("a의 범위에 속하지 않는다.");
}
3)while 문 조건이 참인 동안 계속 반복 실행하며 조건 기반 반복에 적합함
4) For 문
정해진 횟수만큼 반복 실행하며 범위 기반 반복에 적합함
5) Break, Continue 문
- Break: switch, for, while, do - while의 루프에서 탈출시키기 위한 문
- continue: 다시 while 문으로 간다. (e,g, 특정 조건에서 코드 실행 안하고 while 문으로 가게끔)
7. 7. 배열
- 동일한 자료형의 데이터를 연속된 메모리 공간에 저장하는 자료 구조- 배열의 크기는 초기화 시 정해지며(메모리 할당의 안정성을 위해..) 초기화 시 값을 {1, 2, 3, 4, 5} 이런 식으로 넣어줄 수 있다.
- 초기화 후에는 a[0] = 1; 이런식으로 인덱스 별로 값을 변경해주어야 한다.
1) 1차원 배열
<cpp />
int a[5]; // 초기화 없이 선언만 해줘도 되어야 하나 size는 지정해 주어야 한다.
int a[] = {1,2,3,4,5}; // 배열의 크기[5]를 지정하지 않아도 자동으로 4x5 -> 20 byte 할당
int b[5] = {1,2,3}; // 채워지지 않은 부분은 모두 0으로 초기화되며 크기보다 더 많이 넣으면 error 발생
배열의 이름은 a[0] 값을 나타낸다. (※ 배열의 주소값은 첫번째 변수의 주소값을 저장하기 때문에)
2) 2차원 배열
<cpp />
int arr[][2]; //첫 번째 행의 크기는 생략할 수 있지만, 두 번쨰 열의 크기는 지정해줘야 한다.
정확하게 해야하므로..
int arr[][2] = { // 3개의 행과 두개의 열을 가짐
{1, 2},
{3, 4},
{5, 6} };
3) 배열의 자료형
배열은 모든 자료형과 사용자 정의 자료형을 사용할 수 있다.
<cpp />
struct Point{
intx;
inty; };
struct Point arr[10];
4) 배열의 크기
sizeof(arr) / sizeof(arr[0]); // 배열이 메모리 상 차지하는 size / 요소 하나의 size -> 배열 인자의 개수(크기)
문자열 배열일 경우 끝에 NULL 값을 포함하고 있어 strlen(arr) 또는 -1을 해주어야 한다.
5) 함수 인자
배열이 함수의 인자로 전달할 때 배열 자체를 전달하는 것이 아닌 주소(포인터)가 전달된다.
따라서 배열의 전체 길이를 구하려면 strcpy 등을 통해 복사해 배열을 만들어 길이를 구해야 하며
char *str == char str[] //str[]이 첫 번째의 주소값과 동일하므로..
str[0] = 'H'; //이런 식으로 수정이 가능하며 main 함수에도 적용된다.
8. 8. 포인터
1) 포인터어떤 데이터가 차지하는 메모리의 주소를 저장하기 위한 변수이다.
포인터 변수는 해당 변수의 자료형과 동일하게 되며 주소 표시인 *을 붙인다.
그리고 p+1 을 할 경우 자료형의 byte 만큼 이동한다.
포인트 변수의 주소는 CPU가 64 bit 주소를 사용할 경우 8 bytes
32bit 주소를 사용할 경우 4 bytes가 된다. (해당 길이가 가장 효율적 이기 때문)
2) 포인터 변수 개념
<cpp />
int i = 10;
int* p; //p를 포인터 변수로 선언
p = &i; //변수 주소를 포인터(주소) 변수에 저장 (p = 0x7ffdb6e7b1fc)
*p = 20; //*p는 i와 똑같음(해당 주소의 값이니)
&p = &i; //c에서 변수 주소 변경 및 설정은 불가능
0으로 초기화 또는 선언만 된 상태의 포인터 변수의 값 *p을 가져오면 프로세스가 죽는다.
배열을 포인터 변수에 넣으면 포인터 변수 p는 배열과 동일하게 작동하게 된다.
(컴파일러에 의해서 변수가 동일하게 작동하는 것이지 sizeof(p), sizeof(a)의 값은 다르며 아예 같은 타입이 되는건 아니다.)
<cpp />
int a[5] = {1,2,3,4,5};
int* p = a; // p[3] == a[3];
p = &a[0]; or a; or &a[4] 어떤 곳의 주소값을 넣어도 된다.
*ptr == a[0], *(ptr+1) == a[1] 이 된다.
3) 포인터 연산
곱/나누기는 불가능
4) 포인터 다차원 배열
int a[3][2]; //3행 2열
우선 a에는 a[0]의 주소가 저장된다. a의 타입은 2개의 열을 가진 배열의 주소 이므로 int (*)[2]이다.
a[0]에는 a[0][0]의 주소가 저장된다. a[0]의 타입은 1개의 열을 가진 배열의 주소 이므로 int* 이다.
따라서 포인터 변수를 할당할 때 int (*)[2]에 맞춰 아래와 같이 할당해야한다.
int (*p)[2] = a; //2개의 int로 구성된 배열을 가리키는 포인터
2차원 배열 a의 이름에는 첫번째 행의 시작 주소인 즉 값이 아닌 배열을 담고 있는 포인터(&a[0]) 값이 저장된다.
(2개의 int를 담고 있는 배열을 가리키는 포인터)
이 때 a의 타입은 int (*)[2]가 된다.
이 때 포인터 변수의 타입은 int {*p)[2] 가 된다.
*(p + 1) -> 한 행 단위로 움직인다.([2] 일 때는 4*2 byte 씩 이동)
5) 포인터 배열
<cpp />
int *p[3] = {&a, &b, &c};
6) 다중 포인터
<cpp />
int a = 10;
int* p1 = *a;
int** p2 = &p1;
7) 함수 포인터 배열
<cpp />
int (*p[2])();
p[0] = ∑ //함수1
p[1] = − //함수2
p[0](); //함수1 호출
p[1](); //함수2 호출
9. 9. 함수
1) Call by address -> 주소로 값(Arguments)를 전달호출 함수와 호출 되는 함수는 다른 메모리 영역에 존재해 주소로 값(인자)을 전달해야 호출 되는 함수에서 해당 인자에 대한
값 변환이 가능하다.
2) 재귀함수 - 자기 자신을 여러 번 호출
int factorial(int f)
{
if ( f == 1)
return 1;
else
return (f * factorial(f - 1 ));
}
10. 10. Scope
1) 메모리 영역힙 영역을 제외하고 텍스트, 데이터, 스택 영역은 정적 할당으로 컴파일 단계(프로그램 실행 전)에
미리 메모리 공간을 할당해 고정된 크기의 메모리가 할당되며 프로그램 실행 중에 변겨오디지 않는다.
- 텍스트 영역 : CPU가 직접 실행하는 기계어 코드(어셈블리) 및 상수를 저장하는 공간(e.g. mov, add, jmp, 10)
여러 프로그램에서 공유가 가능하며 프로그램 실행 중 변경되지 않음
- 데이터 영역 : 프로그램 종료 시까지 유지할 데이터 저장 공간(e.g. 전역 변수, 정적 변수)
프로그램 시작 초기화되며 프로그램 실행 중 값을 변경할 수 있다.
- 스택 영역 : 사용하고 버릴 데이터 저장 공간(e.g. 지역 변수, 함수 매개 변수, 함수 종료 후 돌아갈 주소)
정적 할당 방식을 사용하지만 함수 호출 시 동적으로 메모리가 생성되고 함수 반환 시 소멸된다.
실행 중 값을 변경할 수 있으며, LIFO(Last in, First Out)로 처리된다.
- 힙 영역 : 언제든 할당하고 사용 후 해제할 수 있는 데이터 저장 공간(malloc()으로 할당, free()로 해제)
모든 데이터를 동적으로 설정할 수 있다. FIFO(First in, First Out) 으로 처리됨
2) 전역 변수
전역 변수는 헤더 파일에 변수를 생성하고 #include를 통해 원하는 소스파일에서 사용하는게 일반적임
프로그램 실행 전에 정적 초기화(초기값이 있다면 입력, 초기값이 없으면 데이터 타입에 따른 기본값(0, Null 등)으로 입력됨)
데이터 메모리 영역에 존재하게 되어 프로그램이 종료될 때까지 사용이 가능하다.
프로그램 전체에서 공유해야 하는 데이터 저장에 적합
3) 지역 변수
선언된 블록 안에서만 유효, 블록 실행 시작 시 생성되고 블록 실행 종료 시 소멸됨(데이터 영역에 저장x)
4) Static
파일 안에 선언하면 파일 안에서만 사용 가능한 함수 또는 변수, 함수 안에 선언하면 함수 안에서만 사용 가능한 변수이며
함수가 처음 호출될 때 동적 초기화 된다.(초기값이 있다면 입력, 초기값이 없으무조건 0으로 입력됨)
메모리는 함수가 호출될 때 생성되고 끝나면 소멸된다. 스텍 메모리에서는 해제되지만, 변수의 값은 데이터 메모리 영역에
복사되기 떄문에 다시 호출되면 메모리가 다시 생성되며 이전 값을 유지한다.
특정 파일, 함수 에서만 사용하되 이전 호출 값을 유지해야 하는 경우에 적합
5) Extern
다른 파일에 정의된 변수 또는 함수를 연결하여 파일 간 변수를 공유해 사용할 수 있는 하는 역할
1) 기본 형식
<cpp />
struct student {
int student_id;
char student_name[10];
int age;
};
struct student stu1 = {1, "jungah"};
2) tag 추가 형식
<cpp />
struct student {
int student_id;
char student_name[10];
int age;
}stu1 = {1, "jungah", 32};
struct student stu2;
3) typedef 형식
<cpp />
typedef struct {
int student_id;
char student_name[10];
int age;
} stu3;
4) 포인터 사용
<cpp />
struct student *p = &stu1;
printf("%d", p->student_id);
5) nested 구조체
구조체 위에 구조체를 선언
<cpp />
struct city{
int home_id;
char home_name[10];
}city1 = {1,"강남"};
struct student{
struct city home;
int student_id;
char student_name[10];
}stu1 = {city1, 1, "hyungjoo"};