C 언어 핵심 문법과 메모리 관리 완벽 가이드

1. 데이터 타입

중요 포인트

  • return 은 함수 실행을 종료하고, 결과 값을 EAX 레지스터에 저장한 후 반환합니다.
  • 서로 다른 기능을 수행하는 코드 블록 사이에는 공백 줄을 넣어 가독성을 높입니다.
  • 코드 내에서 주석 스타일은 일관되게 유지합니다.
  • /* */ 블록 주석은 중첩하여 사용할 수 없습니다.
  • long long 자료형은 C99 표준에서 도입되었습니다.
  • extern int data; 는 컴파일러에게 "이 변수는 int 타입이며, 다른 파일에 정의되어 있다"고 알려주는 선언입니다.

핵심 용어

  • 선언 (Declaration): 메모리를 할당하지 않고, 컴파일러에게 변수의 타입과 이름을 알려줍니다.
  • 정의 (Definition): 변수를 위해 실제 메모리 공간을 할당합니다.
  • 사용 (Use): 변수에 대한 읽기 또는 쓰기 연산을 의미합니다.
  • 초기화 (Initialization): 변수를 선언하면서 동시에 초기값을 설정하는 것입니다.
extern int num;
void test()
{
    printf("%d\n", num);
}
int num; // 실제 메모리 공간이 할당되는 정의

변수명 규칙

  • 영문자, 숫자, 밑줄(_)을 사용할 수 있으며, 숫자로 시작할 수 없습니다.
  • C 언어의 예약어(keyword)는 변수명으로 사용할 수 없습니다.
  • 자료형은 메모리 공간의 크기를 결정하고, 변수명은 해당 공간을 식별하는 이름입니다.

문자 자료형

  • 소문자 a부터 z는 ASCII 코드 97부터 122까지의 값을 가집니다.
  • 대문자 A부터 Z는 ASCII 코드 65부터 90까지의 값을 가집니다.
  • 같은 알파벳의 대문자와 소문자는 ASCII 코드 값이 32만큼 차이납니다.
  • char 타입 변수 초기화의 권장 방법: char ch = '\0';
  • 문자 자료형에 저장되는 실제 값은 해당 문자의 ASCII 코드 값입니다. 컴퓨터는 모든 데이터를 숫자로 처리하며, 출력 시에만 문자 형태로 변환하여 보여줍니다.
  • 문자 상수('a')는 크기가 1바이트이고, 문자열 상수("a")는 문자열 끝의 널 문자(\0)를 포함하여 2바이트의 크기를 가집니다.
  • 큰따옴표(")는 문자열을 나타내며, 해당 문자열의 첫 번째 문자의 주소 값을 의미합니다.
  • 작은따옴표(')는 문자를 나타내며, 해당 문자의 ASCII 코드 값을 의미합니다.
  • int num = '1234'; 와 같은 다중 문자 상수는 이식성 문제가 있을 수 있으므로 주의해야 합니다.

실수 자료형

  • C++에서 f 접미사 없이 실수를 사용하면 기본적으로 double 타입으로 간주됩니다.
  • float 타입 변수의 권장 초기화 방법: float f = 0.0f;
  • C++의 coutfloatdouble 모두 기본적으로 소수점 이하 5자리까지 출력합니다.

이스케이프 시퀀스

  • 기본 이스케이프: '\0' (널 문자), '\n' (줄 바꿈), '\t' (탭), '\r' (캐리지 리턴), '\a' (경고음)
  • 8진수 이스케이프 '\ddd': d는 0~7 사이의 숫자이며, 최대 3자리의 8진수 값을 나타냅니다.
  • 16진수 이스케이프 '\xhh': h는 0~9, a~f 사이의 문자이며, 최대 2자리의 16진수 값을 나타냅니다.

데이터 타입 변환

  • 컴파일러는 정밀도 손실을 방지하기 위해 작은 크기의 데이터 타입을 자동으로 더 큰 크기의 데이터 타입으로 변환합니다. 예: intlongfloatdouble
  • charshort 타입은 연산 시 자동으로 4바이트(int)로 변환됩니다.
  • unsigned 타입과 signed 타입이 섞여 연산될 경우, signed 타입이 unsigned 타입으로 변환된 후 연산이 수행됩니다.

구조체

기본 자료형(char, short, int, long, float, double, unsigned, signed) 외의 복합 데이터 타입입니다.

struct Person
{
    int age;
    char name[32];
}; // 세미콜론 필수
  • 구조체 배열 초기화: memset(arr, 0, sizeof(arr));
  • 멤버 오프셋 구하기: (size_t)&((struct Person*)0)->age
  • 같은 타입의 구조체 변수 간 대입(=)은 내부적으로 memcpy 방식으로 동작합니다.
  • 얕은 복사와 깊은 복사: 구조체 내에 포인터 멤버가 있을 경우 발생할 수 있습니다.
    • 변수 자체의 내용은 모두 복사되지만, 포인터가 가리키는 동적 할당 메모리 영역은 복사되지 않아 두 구조체가 동일한 메모리를 가리키게 됩니다.
    • 이로 인해 동일한 메모리를 두 번 해제하는 오류(double free)가 발생하거나, 데이터가 의도치 않게 덮어씌워질 수 있습니다.
  • 메모리 정렬 (Alignment)
    1. 컴파일러는 구조체의 각 멤버를 특정 단위(alignment unit)에 맞춰 정렬합니다.
    2. 각 멤버의 시작 주소(offset)는 해당 멤버 타입의 크기의 배수여야 합니다.
    3. 구조체 전체 크기는 가장 큰 멤버의 크기의 배수여야 합니다.
    4. double 타입 멤버는 8바이트 정렬을 따르지만, 일부 컴파일러 설정에 따라 4바이트 단위로 정렬될 수도 있습니다.
  • 중첩 구조체 메모리 정렬: 내부 구조체의 가장 큰 멤버를 기준으로 정렬이 이루어집니다.
  • 강제 정렬: #pragma pack (value) 를 사용하여 정렬 단위를 변경할 수 있습니다.
    1. 할당 단위는 구조체 내 가장 큰 기본 타입 크기와 value 중 작은 값으로 결정됩니다.
    2. 이 값을 기준으로 각 멤버의 오프셋이 결정됩니다.

비트 필드 (Bit-field)

  • 비트 필드의 주소를 구할 수 없습니다. 메모리는 바이트 단위로 주소가 할당되기 때문입니다.
  • 비트 필드의 총 비트 수는 해당 멤버의 타입이 가질 수 있는 비트 수를 초과할 수 없습니다.
  • 표준적인 비트 필드의 타입은 unsigned int 이지만, signed/unsigned char, short, int, long 도 사용 가능합니다.
  • 비트 필드에 값을 할당할 때는 필드의 크기를 초과하지 않도록 주의해야 합니다. 상위 비트는 무시됩니다.
  • 비트 필드가 signed 타입으로 선언되면, 값을 읽을 때 부호 확장(sign extension)이 적용됩니다.
  • 인접한 동일 타입의 비트 필드는 하나의 메모리 단위로 합쳐질(압축) 수 있습니다.
  • unsigned char : 0; 을 사용하면 다음 비트 필드가 새로운 메모리 단위에서 시작하도록 강제할 수 있습니다.
  • unsigned char : 2; 와 같이 이름 없는 비트 필드는 패딩(padding) 용도로 사용됩니다.

공용체 (Union)

모든 멤버가 동일한 메모리 공간을 공유합니다. 공용체의 전체 크기는 가장 큰 멤버의 크기로 결정됩니다.

열거형 (Enum)

변수가 가질 수 있는 값을 기호 상수(symbolic constant)로 정의합니다. 기본값은 0부터 시작하여 1씩 증가합니다. 중간에 값을 할당하면 이후 값은 그 값부터 증가합니다.

enum Color { RED, GREEN, BLUE };
enum Color myColor = RED; // C++에서는 열거형 이외의 값 할당 시 오류 발생 가능

2. 진법 변환과 입출력

진법별 입출력

표준 C 언어는 2진수 입력을 직접 지원하지 않지만, 일부 최신 컴파일러는 확장 기능을 제공합니다.

진법 입력 접두사 출력 형식 지정자
2진수 0b (컴파일러 확장) #include <bitset>, std::bitset<8>(x)
8진수 0 %o (oct)
10진수 (기본값) %d (dec)
16진수 0x %x (hex)

진법 변환 규칙

  1. 10진수 → 2/8/16진수: 2, 8, 16으로 나누는 '短除法' (연속 나눗셈)을 사용합니다.
  2. 2/8/16진수 → 10진수: 각 자리값에 자릿수만큼의 진법의 거듭제곱을 곱하여 더합니다.
  3. 2진수 ↔ 8진수: 소수점을 기준으로 2진수를 3자리씩 묶어 8진수로, 8진수 각 자리를 3자리 2진수로 변환합니다.
  4. 2진수 ↔ 16진수: 소수점을 기준으로 2진수를 4자리씩 묶어 16진수로, 16진수 각 자리를 4자리 2진수로 변환합니다.
  5. 8진수와 16진수 간 직접 변환: 2진수를 거쳐서 변환합니다.

3. 원점수, 1의 보수, 2의 보수

개념

  • 원점수 (Sign-Magnitude): 부호 비트(최상위 비트, MSB)와 절대값으로 이루어진 가장 직관적인 표현 방식입니다.
  • 1의 보수 (Ones' Complement): 양수는 원점수와 동일하며, 음수는 모든 비트를 반전(0→1, 1→0)하여 표현합니다.
  • 2의 보수 (Two's Complement): 1의 보수에 1을 더하여 표현합니다. 현대 컴퓨터에서 음수를 저장하는 표준 방식입니다.

변환 규칙

  • 부호 없는 정수: 원점수 = 1의 보수 = 2의 보수
  • 양수 (부호 있음): 원점수 = 1의 보수 = 2의 보수
  • 음수 (부호 있음):
    • 1의 보수 = 원점수의 부호 비트는 그대로, 나머지 비트는 반전
    • 2의 보수 = 1의 보수 + 1

2의 보수 표현의 의미

  1. 뺄셈을 덧셈으로 처리할 수 있게 합니다.
  2. +0-0이라는 두 개의 0 표현을 하나로 통일합니다. (2의 보수에서는 0이 하나만 존재, 10000000-128을 의미)

데이터 저장

  • 컴퓨터는 모든 정수를 2의 보수 형태로 메모리에 저장합니다.
  • 8진수, 16진수는 일반적으로 부호 개념이 없으며, 메모리에 저장된 비트 패턴 그대로를 의미합니다.
  • 예를 들어, -10을 32비트 메모리에 저장하면 0xFFFFFFF6 (-10의 2의 보수) 형태로 저장됩니다.

데이터 읽기

  • 부호 없는 변수: 메모리에 저장된 값을 그대로 읽어 옵니다.
  • 부호 있는 변수: 메모리의 최상위 비트(MSB)를 검사합니다.
    • MSB가 0이면: 양수로 간주하여 메모리 값을 그대로 읽습니다.
    • MSB가 1이면: 음수로 간주하여 메모리 값에 대해 다시 2의 보수(보수 + 1)를 취한 결과를 읽습니다.

unsigned int data = -10; 의 경우

-10이 메모리에 0xFFFFFFF6로 저장됩니다. 이 값을 unsigned int로 읽으면 4294967286이라는 큰 양수로 출력됩니다. 하지만 signed int로 강제 형변환하여 읽으면 -10이 출력됩니다.

4. 주요 키워드

const

  • const로 선언된 변수는 읽기 전용(read-only)이 됩니다.
  • 변수명을 통해 직접 값을 변경할 수는 없지만, 포인터를 통해 간접적으로 값을 변경할 수 있습니다. (단, 실제로 변경 가능한지는 변수가 저장된 메모리 영역에 따라 다릅니다.)
  • 상수로 초기화된 const 변수 (C++): C++에서는 컴파일러가 const int data = 10; 과 같은 경우, 즉시 메모리를 할당하지 않고 값 10을 '상수 테이블'에 등록합니다. 사용자가 &data와 같이 주소를 참조하는 순간에 메모리가 할당됩니다.
  • C와 C++의 차이: C 언어의 const 변수는 '읽기 전용 변수'로, 포인터를 통해 값을 변경하는 것이 문법적으로 가능하지만, C++에서는 '진정한 상수'로 취급되어 컴파일 타임에 값이 상수로 사용됩니다.
  • 지역 const 변수: 스택 영역에 저장되므로 포인터로 값 변경이 가능할 수 있습니다. 하지만 일부 컴파일러는 최적화를 위해 const 변수 사용 시 메모리에서 값을 읽지 않고 상수 값을 직접 사용하기 때문에, 포인터로 값을 변경해도 반영되지 않는 것처럼 보일 수 있습니다.
  • 전역 const 변수: 읽기 전용 데이터(.rodata) 세그먼트에 저장되어 수정이 불가능합니다. 포인터를 통해 값 변경을 시도하면 세그먼테이션 오류(Segmentation fault)가 발생합니다.
  • 변수로 초기화된 const 변수: int b = 10; const int data = b; 와 같은 경우, 컴파일러는 data를 위한 메모리를 즉시 할당합니다.
  • const와 포인터
    • const int *p 또는 int const *p: p가 가리키는 값을 변경할 수 없습니다. (*p 읽기 전용)
    • int * const p: 포인터 p 자체를 변경할 수 없습니다. (다른 주소를 가리킬 수 없음, *p 변경 가능)
    • const int * const p: p가 가리키는 값과 p 자체 모두를 변경할 수 없습니다.

register

  • 자주 사용되는 변수를 CPU 레지스터에 저장하도록 컴파일러에 힌트를 주는 키워드입니다.
  • 컴파일러가 판단하여 레지스터에 저장하지 않을 수도 있습니다.
  • 레지스터 변수의 주소는 구할 수 없습니다. (C++에서는 컴파일러가 자동으로 일반 변수로 변경할 수 있음)

volatile

  • 컴파일러의 최적화를 방지하여, 변수를 읽거나 쓸 때마다 항상 메모리에 직접 접근하도록 지시합니다.
  • 주로 하드웨어 레지스터나 인터럽트 서비스 루틴(ISR)에서 변경되는 변수에 사용됩니다.

sizeof

  • sizeof 연산자는 데이터 타입이나 변수의 메모리 크기(바이트 단위)를 반환합니다.
  • 문자열의 길이를 반환하는 strlen과는 다릅니다.
  • sizeof('a') 는 C 언어에서 문자 상수 'a'int 타입으로 간주하여 4를 반환합니다.

typedef

  • 기존 데이터 타입에 새로운 이름(별칭)을 부여합니다. 새로운 타입을 생성하는 것은 아닙니다.
  • 별칭은 보통 대문자로 작성하여 가독성을 높입니다.
  • 사용 방법
    1. 기존 타입을 사용하여 변수를 선언합니다. 예: int myArray[5];
    2. 변수명을 원하는 별칭으로 바꿉니다. 예: int ArrayType[5];
    3. 맨 앞에 typedef 키워드를 추가합니다. 예: typedef int ArrayType[5];
  • 사용 예: typedef int ARRAY_TYPE[5]; ARRAY_TYPE data = {1,2,3,4,5};

static

  • 지역 변수에 사용: 함수 내에서 static으로 선언된 변수는 프로그램이 시작될 때 한 번만 초기화되며, 함수 호출이 끝나도 메모리에 남아 값을 유지합니다.
  • 전역 변수/함수에 사용: 선언된 소스 파일 내에서만 접근 가능하도록 범위를 제한합니다. (내부 연결, internal linkage)

5. 연산자

산술 연산자

  • /: 두 정수 간 연산 시 몫을 반환하고, 실수가 포함되면 일반적인 나눗셈을 수행합니다.
  • %: 나머지 연산자. 피연산자는 정수만 가능합니다.
  • +=, -= 등 복합 대입 연산자: a *= b + 5a = a * (b + 5) 와 동일합니다.

관계 연산자

  • >, <, ==, >=, <=, != 는 값의 크기를 비교합니다. 문자열의 주소 비교가 아닌 내용 비교를 위해서는 strcmp() 함수를 사용해야 합니다.

논리 연산자와 단축 평가 (Short-circuit Evaluation)

  • && (논리 AND): 첫 번째 피연산자가 false이면 두 번째 피연산자를 평가하지 않고 false를 반환합니다.
  • || (논리 OR): 첫 번째 피연산자가 true이면 두 번째 피연산자를 평가하지 않고 true를 반환합니다.
  • ! (논리 NOT): 0이 아닌 값은 true로 간주합니다.

비트 연산자

연산자 규칙 용도 예시
& (AND) 두 비트가 모두 1일 때만 1 특정 비트를 0으로 클리어 data &= ~(1 << 3); (3번 비트를 0으로)
| (OR) 두 비트 중 하나라도 1이면 1 특정 비트를 1로 세트 data |= (1 << 5); (5번 비트를 1로)
^ (XOR) 두 비트가 서로 다르면 1 특정 비트를 토글 data ^= (1 << 2); (2번 비트를 반전)

시프트 연산자

  • 음수에 대한 오른쪽 시프트(>>)는 산술 시프트(arithmetic shift)가 적용되어 최상위 비트(부호 비트)가 채워집니다.
  • 시프트할 비트 수가 피연산자의 비트 폭보다 크면, 실제 시프트되는 비트 수는 피연산자의 비트 폭으로 나눈 나머지 값입니다.

삼항 연산자

  • condition ? value_if_true : value_if_false 형태로 사용합니다.

증감 연산자

  • 단독 문장으로 사용될 때는 ++ii++의 결과가 동일합니다.
  • ++i (전위, prefix): 값을 먼저 증가시킨 후, 증가된 값을 식에 사용합니다.
  • i++ (후위, postfix): 현재 값을 식에 먼저 사용한 후, 값을 증가시킵니다.

쉼표 연산자

가장 낮은 우선순위를 가지며, 왼쪽에서 오른쪽으로 순차적으로 평가되고 마지막 표현식의 결과가 전체 결과가 됩니다.

int num;
num = 10, 20, 30, 40;   // num은 10 (대입 연산자가 쉼표 연산자보다 우선)
num = (10, 20, 30, 40); // num은 40 (괄호 안에서 쉼표 연산자가 평가됨)

6. 유용한 표준 라이브러리 함수

난수 생성

함수 헤더 기능
time_t time(time_t *tloc) <time.h> 1970년 1월 1일 이후 경과된 초 단위 시간을 반환합니다.
void srand(unsigned int seed) <stdlib.h> 난수 발생기의 시드(seed) 값을 설정합니다. 루프 안에 두지 않습니다.
int rand(void) <stdlib.h> 0부터 RAND_MAX 사이의 의사 난수(pseudo-random)를 반환합니다.
#include <stdlib.h>
#include <time.h>

void generate_random() {
    srand(time(NULL)); // 시간을 시드로 사용
    int r1 = rand();
    int r2 = rand();
    printf("Random numbers: %d, %d\n", r1, r2);
}

동적 메모리 관리 (<stdlib.h>)

함수 기능
malloc(size_t size) 지정된 크기(size 바이트)만큼 메모리를 할당합니다. 초기화되지 않은 상태입니다.
calloc(size_t nmemb, size_t size) nmemb 개의 요소 각각 size 바이트 크기만큼 메모리를 할당하고, 모든 비트를 0으로 초기화합니다.
realloc(void *ptr, size_t size) 이전에 할당된 메모리 블록의 크기를 size로 변경합니다. 확장 또는 축소 가능합니다.
free(void *ptr) 이전에 할당된 메모리 블록을 해제합니다.
  • 주의: 해제된 메모리를 다시 해제하려고 하면 정의되지 않은 동작(undefined behavior)이 발생합니다.
    if (ptr != NULL) {
        free(ptr);
        ptr = NULL; // 해제 후 포인터를 NULL로 설정하여 안전하게 관리
    }
    

문자열 처리 함수 (<string.h>)

함수 기능
strlen(str) 문자열의 길이를 반환합니다. (널 문자 제외)
strcpy(dest, src) 문자열을 복사합니다.
strncpy(dest, src, n) 문자열의 앞 n개 문자를 복사합니다.
strcat(dest, src) 문자열을 이어 붙입니다.
strncat(dest, src, n) 문자열의 앞 n개 문자를 이어 붙입니다.
strcmp(s1, s2) 두 문자열을 비교합니다. (사전순)
strchr(str, c) 문자열에서 특정 문자(c)를 왼쪽부터 검색합니다.
strrchr(str, c) 문자열에서 특정 문자(c)를 오른쪽부터 검색합니다.
strstr(haystack, needle) 문자열 haystack에서 문자열 needle이 처음 나타나는 위치를 찾습니다.

메모리 조작 함수 (<string.h>)

함수 기능
memset(ptr, value, num) 메모리 블록을 특정 값(value)으로 채웁니다. 주로 초기화에 사용됩니다.
memcpy(dest, src, num) 메모리 블록을 다른 블록으로 복사합니다. (메모리 영역이 겹치면 안 됨)
memcmp(ptr1, ptr2, num) 두 메모리 블록의 첫 num 바이트를 비교합니다.

문자열과 숫자 변환 (<stdlib.h>)

함수 기능
atoi(str) 문자열을 int 타입으로 변환합니다.
atol(str) 문자열을 long 타입으로 변환합니다.
atof(str) 문자열을 double 타입으로 변환합니다.

문자열 포맷 함수 (<stdio.h>)

  • sscanf%s 포맷은 공백, 개행, 널 문자를 만나면 읽기를 중단합니다.
  • 포맷 지정 옵션
    • %[width]d / %[width]s: 지정된 너비만큼만 데이터를 읽습니다.
    • %*d / %*s: 데이터를 읽지만, 변수에 저장하지 않고 건너뜁니다.
    • %*[width]d / %*[width]s: 지정된 너비만큼 읽고 건너뜁니다.
    • %[a-z]: 주어진 문자 집합(a부터 z)에 포함된 문자만 읽습니다.
    • %[aBc]: 주어진 문자들(a, B, c) 중 하나와 일치하는 문자만 읽습니다.
    • %[^aFc]: 캐럿(^) 다음에 나오는 문자(a, F, c)를 제외한 문자를 읽습니다.
    • %[^a-z]: a부터 z를 제외한 문자를 읽습니다.
함수 기능
sprintf(buffer, format, ...) 포맷된 문자열을 buffer 배열에 출력(저장)합니다.
fprintf(stream, format, ...) 포맷된 문자열을 파일 스트림(stream)에 출력합니다.
sscanf(buffer, format, ...) buffer 배열에서 포맷에 맞게 데이터를 읽어 변수에 저장합니다.
fscanf(stream, format, ...) 파일 스트림(stream)에서 포맷에 맞게 데이터를 읽어 변수에 저장합니다.

파일 입출력 함수 (<stdio.h>)

  • 텍스트 모드("t")로 파일을 읽을 때는 EOF가 파일의 끝을 나타냅니다.
  • 바이너리 모드("b")로 파일을 읽을 때는 feof() 함수를 사용하여 파일의 끝을 확인합니다.
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
    perror("Error opening file");
    return -1;
}
// 파일 읽기/쓰기 작업 수행
fclose(fp); // 파일 닫기
함수 기능
fopen(filename, mode) 파일을 열고 FILE* 포인터를 반환합니다.
fclose(fp) 열린 파일을 닫습니다.
fputc(c, fp) 파일에 한 문자를 씁니다.
fgetc(fp) 파일에서 한 문자를 읽습니다.
feof(fp) 파일 포인터가 파일의 끝에 도달했는지 확인합니다. (도달 시 0이 아닌 값 반환)
fputs(str, fp) 파일에 문자열을 씁니다. (성공 시 0, 실패 시 EOF 반환)
fgets(str, n, fp) 파일에서 최대 n-1개의 문자를 읽어 str에 저장합니다. (실패 시 NULL 반환)
fwrite(ptr, size, nmemb, fp) 메모리 블록(ptr)에서 파일로 데이터를 씁니다. 반환값은 성공적으로 쓴 '항목의 개수'입니다.
fread(ptr, size, nmemb, fp) 파일에서 데이터를 읽어 메모리 블록(ptr)에 저장합니다. 반환값은 성공적으로 읽은 '항목의 개수'입니다.
fprintf(fp, format, ...) 포맷된 데이터를 파일에 씁니다.
fscanf(fp, format, ...) 파일에서 포맷된 데이터를 읽습니다.
rewind(fp) 파일 위치 지시자를 파일의 시작으로 되돌립니다.
ftell(fp) 현재 파일 위치 지시자의 위치(파일 시작부터의 바이트 단위 오프셋)를 반환합니다.
fseek(fp, offset, whence) 파일 위치 지시자를 특정 위치로 이동시킵니다.

fseek 함수 상세

int fseek(FILE *stream, long offset, int whence);
// whence 값:
//   SEEK_SET (0): 파일의 시작점을 기준으로 offset만큼 이동 (offset은 양수)
//   SEEK_CUR (1): 현재 위치를 기준으로 offset만큼 이동
//   SEEK_END (2): 파일의 끝을 기준으로 offset만큼 이동 (offset은 음수)

태그: C언어 데이터타입 메모리관리 포인터 const

8월 12일 09:51에 게시됨