C언어 간단 문법 정리

C언어 학습 1주차: 핵심 문법 다시보기

이번 주에는 C언어의 기본적인 문법 중에서 혼동하기 쉽거나 중요한 몇 가지 주제를 다시 정리했습니다. 변수의 종류와 생존 범위, 메모리 관리에 필수적인 포인터와 배열, 그리고 비트 단위 연산까지 깊이 있게 살펴보겠습니다.

1. 변수의 종류와 범위

C언어에서 변수는 선언된 위치와 방법에 따라 지역 변수, 전역 변수, 정적 변수(static)로 나뉩니다. 각 변수는 메모리상에서 다른 생존 기간(Life Cycle)과 접근 범위(Scope)를 가집니다.

1.1. 지역 변수 (Local Variables)

함수나 코드 블록({}) 내에서 선언된 변수입니다.

  • 생존 기간: 해당 블록이 실행될 때 생성되고, 블록이 끝나면 자동으로 소멸합니다.
  • 접근 범위: 선언된 블록 내에서만 접근할 수 있습니다.
#include <stdio.h>

void myFunction() {
    int local_var = 10; // myFunction 안에서만 사용 가능한 지역 변수
    printf("myFunction의 지역 변수: %d\n", local_var);
}

int main() {
    int main_local_var = 20; // main 함수 안에서만 사용 가능한 지역 변수
    myFunction();
    printf("main의 지역 변수: %d\n", main_local_var);
    // printf("%d", local_var); // 컴파일 에러: 다른 함수의 지역 변수에는 접근 불가
    return 0;
}

1.2. 전역 변수 (Global Variables)

함수 외부에서 선언된 변수입니다.

  • 생존 기간: 프로그램이 시작될 때 생성되어 끝날 때까지 유지됩니다.
  • 접근 범위: 프로그램 전체(모든 파일)에서 접근할 수 있습니다. 단, 다른 파일에서 사용하려면 extern 키워드로 선언해야 합니다.
#include <stdio.h>

int global_var = 100; // 전역 변수

void anotherFunction() {
    printf("anotherFunction에서 접근: %d\n", global_var);
}

int main() {
    printf("main에서 접근: %d\n", global_var);
    anotherFunction();
}

1.3. 정적 변수 (Static Variables)

static 키워드를 사용하여 선언하는 변수입니다. 선언 위치에 따라 두 가지로 나뉩니다.

1) 함수 내부에 선언된 정적 변수

  • 생존 기간: 전역 변수처럼 프로그램이 끝날 때까지 유지됩니다.
  • 접근 범위: 지역 변수처럼 선언된 함수 내에서만 접근 가능합니다.
  • 특징: 함수 호출이 끝나도 값이 사라지지 않고 유지됩니다. 함수가 다시 호출되면 이전 값을 기억하고 있습니다.
#include <stdio.h>

void count_calls() {
    static int counter = 0; // 정적 지역 변수, 최초 1회만 0으로 초기화
    int normal_counter = 0; // 일반 지역 변수

    counter++;
    normal_counter++;

    printf("Static counter: %d, Normal counter: %d\n", counter, normal_counter);
}

int main() {
    count_calls(); // Static: 1, Normal: 1
    count_calls(); // Static: 2, Normal: 1
    count_calls(); // Static: 3, Normal: 1
    return 0;
}

2) 파일 범위의 정적 변수/함수

  • 생존 기간: 전역 변수와 동일합니다.
  • 접근 범위: 선언된 파일 내에서만 접근할 수 있습니다. 다른 파일에서는 extern으로도 접근할 수 없어, 변수나 함수의 가시성을 제한하는 용도로 사용됩니다. (정보 은닉)
// main.c
#include <stdio.h>

static int file_static_var = 50; // 이 파일(main.c)에서만 접근 가능

static void myStaticFunction() { // 이 파일(main.c)에서만 호출 가능
    printf("myStaticFunction 호출됨\n");
}

int main() {
    printf("파일 정적 변수: %d\n", file_static_var);
    myStaticFunction();
    return 0;
}

2. 비트 연산자 (Bitwise Operators)

데이터를 비트(bit) 단위로 조작할 때 사용하는 연산자입니다. 주로 하드웨어 제어, 데이터 압축, 암호화 등 저수준 프로그래밍에 유용합니다.

연산자 이름 설명 예시 (a=5, b=3)
& AND 두 비트가 모두 1일 때 1 0101 & 0011 = 0001 (1)
| OR 두 비트 중 하나라도 1이면 1 0101 | 0011 = 0111 (7)
^ XOR 두 비트가 다를 때 1 0101 ^ 0011 = 0110 (6)
~ NOT 비트를 반전 (0은 1로, 1은 0으로) ~0101 = ...11111010 (-6)
<< Left Shift 비트를 왼쪽으로 지정된 수만큼 이동 0101 << 1 = 1010 (10)
>> Right Shift 비트를 오른쪽으로 지정된 수만큼 이동 0101 >> 1 = 0010 (2)
#include <stdio.h>

int main() {
    unsigned char a = 5;  // 0000 0101
    unsigned char b = 3;  // 0000 0011

    printf("a & b = %d\n", a & b);   // 1 (0000 0001)
    printf("a | b = %d\n", a | b);   // 7 (0000 0111)
    printf("a ^ b = %d\n", a ^ b);   // 6 (0000 0110)
    printf("~a = %d\n", ~a);         // (결과는 시스템에 따라 다름, 보통 -6)
    printf("a << 1 = %d\n", a << 1); // 10 (0000 1010)
    printf("a >> 1 = %d\n", a >> 1); // 2 (0000 0010)
    return 0;
}

3. 기타 주요 키워드

3.1. const

변수를 상수(constant)로 만들어 값을 변경할 수 없게 합니다. 의도치 않은 값의 변경을 막아 코드의 안정성을 높입니다.

const int MY_CONST = 100;
// MY_CONST = 200; // 컴파일 에러: const 변수는 수정 불가

3.2. sizeof

자료형이나 변수의 크기를 바이트(byte) 단위로 반환하는 연산자입니다.

자료형 크기 (64비트 시스템 기준) 범위 (대략)
char 1 byte -128 ~ 127
short 2 bytes -32,768 ~ 32,767
int 4 bytes -21억 ~ 21억
long long 8 bytes 매우 큼
unsigned int 4 bytes 0 ~ 42억
float 4 bytes 소수점
double 8 bytes 소수점 (더 정밀)
int* (포인터) 8 bytes (64비트 OS)
int n = sizeof(int); // n은 4가 됨
int arr[10];
int arr_size = sizeof(arr); // arr_size는 4 * 10 = 40이 됨

3.3. enum (열거형)

서로 연관된 정수형 상수들을 이름으로 정의할 때 사용합니다. 코드의 가독성을 높여줍니다.

// enum을 사용하지 않을 때
// const int MONDAY = 0;
// const int TUESDAY = 1;

// enum을 사용할 때
enum Weekday { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY };

enum Weekday today = MONDAY;
if (today == MONDAY) {
    printf("오늘은 월요일입니다.\n");
}

3.4. union (공용체)

여러 멤버 변수가 하나의 메모리 공간을 공유하는 자료구조입니다. 메모리를 절약할 때 유용하지만, 한 번에 하나의 멤버만 사용할 수 있습니다.

#include <stdio.h>

union Data {
    int i;
    float f;
    char str[4];
};

int main() {
    union Data data;
    data.i = 1094861636; // 0x41424344 (ASCII 'ABCD')
    printf("data.i: %d\n", data.i);
    printf("data.f: %f\n", data.f); // 의미 없는 값
    printf("data.str: %s\n", data.str); // "DCBA" (리틀 엔디안 시스템)
    
    data.f = 3.14;
    printf("\nAfter changing f:\n");
    printf("data.i: %d\n", data.i); // 의미 없는 값
    printf("data.f: %f\n", data.f);
    return 0;
}

3.5. struct (구조체)

여러 개의 서로 다른 타입의 데이터를 하나로 묶어 관리할 수 있는 자료형입니다. 변수들을 논리적으로 그룹화할 때 사용합니다.

#include <stdio.h>

struct Student {
    int id;
    char name[20];
    float score;
};

int main() {
    struct Student s1 = { 1001, "Kim", 95.5 };
    printf("학번: %d, 이름: %s, 점수: %.1f\n", s1.id, s1.name, s1.score);

    // 구조체 배열 선언
    struct Student students[2] = {
        {1002, "Lee", 88.0},
        {1003, "Park", 92.3}
    };
    printf("2번째 학생 이름: %s\n", students[1].name);
    return 0;
}

특징 및 활용

  • 구조체는 멤버 변수들을 점(.) 연산자로 접근합니다.
  • 함수의 인자, 반환값, 배열 등 다양한 곳에 활용할 수 있습니다.
  • typedef와 함께 사용하면 타입 이름을 간단하게 만들 수 있습니다.
      typedef struct Student {
          int id;
          char name[20];
          float score;
      } Student;
      Student s2 = {1004, "Choi", 77.7};
    

구조체는 여러 데이터를 하나의 논리적 단위로 묶어 관리할 때 매우 유용하며, C언어에서 객체지향적 프로그래밍의 기초가 되는 개념입니다.

4. 포인터와 배열: 메모리의 이해

C언어의 핵심이자 가장 어려운 부분입니다. 변수가 “메모리 덩어리”라는 개념에서 출발해봅시다.

  • 변수: 특정 크기의 메모리 공간에 붙여진 이름. int a = 4;는 4바이트 공간에 a라는 이름을 붙이고 값 4를 저장한 것입니다.
  • 주소 (&): 변수가 저장된 메모리 공간의 시작 위치. &a는 변수 a의 주소를 반환합니다.
  • 포인터 (*): 다른 변수의 메모리 주소를 저장하는 특별한 변수. 64비트 시스템에서 포인터 변수 자체의 크기는 8바이트입니다.

4.1. 포인터 기본

#include <stdio.h>

int main() {
    int a = 10;      // 1. int형 변수 a 선언, 10으로 초기화
    int* b = NULL;   // 2. int형 포인터 b 선언, NULL로 초기화

    b = &a;          // 3. b에 a의 메모리 주소를 저장

    printf("변수 a의 값: %d\n", a);
    printf("변수 a의 주소: %p\n", &a);
    printf("포인터 b의 값 (a의 주소): %p\n", b);
    printf("포인터 b가 가리키는 값: %d\n", *b); // 4. 역참조(dereference) 연산자 *

    *b = 20;         // 5. b가 가리키는 주소(a)의 값을 20으로 변경
    printf("값 변경 후 a의 값: %d\n", a); // a의 값이 20으로 바뀜

    return 0;
}

설명:

  1. int a = 10;: 4바이트 정수형 공간이 할당되고 그곳에 10이 저장됩니다.
  2. int* b;: 주소를 저장할 8바이트(64비트 기준) 포인터 공간이 할당됩니다.
  3. b = &a;: b는 a의 시작 주소를 값으로 가집니다. 이제 b는 a를 “가리킵니다”.
  4. *b: b가 가진 주소로 찾아가서 그곳의 값을 읽어옵니다. 이를 역참조라고 합니다.
  5. *b = 20;: b가 가진 주소로 찾아가서 그곳의 값을 20으로 덮어씁니다. 결국 a의 값이 바뀌게 됩니다.

4.2. 이중 포인터 (Double Pointer)

포인터 변수 자체도 메모리 공간을 차지하므로, 이 포인터의 주소를 저장하는 또 다른 포인터를 만들 수 있습니다. 이를 이중 포인터라고 합니다.

#include <stdio.h>

int main() {
    int a = 10;
    int* p = &a;     // p는 a를 가리킴
    int** pp = &p;   // pp는 p를 가리킴

    printf("a = %d\n", a);
    printf("*p = %d\n", *p);
    printf("**pp = %d\n", **pp); // pp -> p -> a

    **pp = 100; // pp가 가리키는 p가 가리키는 a의 값을 100으로 변경
    printf("After change, a = %d\n", a); // a는 100

    return 0;
}

4.3. 배열과 포인터의 관계

배열의 이름은 그 배열의 첫 번째 요소의 주소를 나타내는 포인터 상수입니다.

#include <stdio.h>

int main() {
    int arr[4] = {10, 20, 30, 40};

    printf("arr[0]의 주소: %p\n", &arr[0]);
    printf("배열의 이름 arr: %p\n", arr); // 위와 동일한 주소

    // 배열 요소 접근
    printf("arr[1] = %d\n", arr[1]);
    printf("*(arr + 1) = %d\n", *(arr + 1)); // 포인터 연산으로 접근

    // arr은 상수이므로 주소를 바꿀 수 없음
    // arr = &some_other_variable; // 컴파일 에러

    return 0;
}

arr[i]는 내부적으로 *(arr + i)와 동일하게 동작합니다. arr은 int형 배열이므로 arr + 1은 arr의 주소에서 sizeof(int)만큼(즉, 4바이트) 뒤의 주소를 의미합니다.

4.4. 다차원 배열과 메모리

2차원 배열 int arr[2][3]은 메모리상에 연속적으로 배치됩니다.

  • arr[0][0], arr[0][1], arr[0][2], arr[1][0], arr[1][1], arr[1][2] 순서로 저장됩니다.
// int arr[2][3];
// 시작 주소가 0x1000이라고 가정하면...

arr[0][0] -> 0x1000
arr[0][1] -> 0x1004
arr[0][2] -> 0x1008
arr[1][0] -> 0x100C (0x1008 + 4)
arr[1][1] -> 0x1010
arr[1][2] -> 0x1014

이처럼 C언어의 문법들은 결국 “메모리를 어떻게 효율적으로 사용하고 관리할 것인가”라는 질문과 연결됩니다. 각 개념을 단순히 외우기보다 메모리 그림을 그려보며 이해하는 것이 중요합니다.

과제 :

https://solved.ac/class/1e

class 1 에센셜 전체 문제

  • https://www.acmicpc.net/problem/1008
  • https://www.acmicpc.net/problem/1330
  • https://www.acmicpc.net/problem/2438
  • https://www.acmicpc.net/problem/2557
  • https://www.acmicpc.net/problem/2562
  • https://www.acmicpc.net/problem/2675
  • https://www.acmicpc.net/problem/2739
  • https://www.acmicpc.net/problem/10818
  • https://www.acmicpc.net/problem/10869
  • https://www.acmicpc.net/problem/10950
  • https://www.acmicpc.net/problem/10951
  • https://www.acmicpc.net/problem/10952
  • https://www.acmicpc.net/problem/11654
  • https://www.acmicpc.net/problem/11720
  • https://www.acmicpc.net/problem/25083
  • https://www.acmicpc.net/problem/27866

results matching ""

    No results matching ""