C - 변수, 자료형, 메모리, 전역변수, extern, static, 배열

markyang92·2024년 3월 11일
0

C

목록 보기
4/10
post-thumbnail

변수

  • 함수 에 선언: 전역
  • 함수 에 선언: 지역

unsigned, signed

  • 기본적으로 unsigned, signed를 명시하지 않으면 signed 취급
    • 예외: char: 컴파일러마다 다름
      • 따라서 char를 아스키(0~127)가 아닌 정수형으로 사용하려고 하면,
        signed, unsigned표기해야함

자료형크기범위printf
char최소 8비트 정수형
  • 표준은 8비트 이상이라고만 정의함
  • %c
    short최소 16비트이고 char크기 이상인 정수형
    int최소 16비트이고 short크기 이상인 정수형
    32bit 컴퓨터가 나오면서 32bit가 됨
    64bit에서도 32bit
    %d
    long최소 32비트이고 int이상의 크기%ld
    floatunsigned: 없음%f
    doubleunsigned: 없음
    long doubleunsigned: 없음
    double 이상의 크기

    char

    • 최소 8비트 정수형
      • 표준은 8비트 이상이라고만 정의함
      • 즉 컴파일러 마다 구현이 달라질 수 있다.

    char가 몇 비트인지 찾는 방법 = 컴퓨터가 1Byte를 몇 비트로 쓰는지 확인

    • limits.h 인클루드 후, CHAR_BIT를 찍어보자.
    #include <limits.h>
    
    int main(void)
    {
    	char char_size = CHAR_BIT;
        return 0;
    }
    • Windows Clang에서 8이라고 뜸
      • 즉 이 환경에서는 char = 8bit = 1Byte
    • CHAR_BIT = char가 차지하는 비트
    • 만약 여기서 16이라고 나오면, 이 컴퓨터는..16bit = 1Byte 쓰는거임.....

    bool

    • C89에선 없음
    • C99부터 들어옴

    enum

    • int와 섞어서 사용 가능
      • int -> enum, enum -> int
      • C에서의 열거형은 그냥 정수에 별명 붙이는 수준
        • 실수를 막을 수 없음
    # C#
    enum EDay { MONDAY, TUESDAY, WEDNESDAY };
    enum EMonth { JANUARY, FEBRUARY, MARCH };
    
    EDay humpDay = EDay.WEDNESDAY;
    EMonth birthMonth = humpDay; // 컴파일 오류
    • C#에서는 컴파일 오류가 떠서 실수 막을 수 있음
    enum day { DAY_MONDAY, DAY_TUESDAY, DAY_WEDNESDAY };
    enum month { MONTH_JANUARY, MONTH_FEBRUARY, MONTH_MARCH };
    
    enum day hump_day = DAY_WEDNESDAY;
    enum moth birth_month = hump_day; /* 컴파일 됨.. */



    sizeof

    • 피연산자의 크기를 Byte로 반환해주는 연산자
      • 함수아니다.
    • return 값은 unsigned 정수형 상수 : size_t

    배열과 sizeof

    int values[30];
    size_t arr_size = sizeof(values); /* 120 */
    • 배열이 차지하는 총 Byte 수를 반환한다.
      • 값형 배열은 스택에 할당되므로 '몇 바이트'를 차지하는지 '컴파일 중' 알기 때문이다.

    매개변수로 전달하는 함수에서는 왜 동작이 다른가?

    • sizeof()로 매개변수로 들어온 배열의 총 바이트 수를 반환할 수 있으려면,
      • 그 배열의 모든 요소가 스택에 다 복사되어 전달돼야 함
    • 그렇다면 매개변수로 전달된 배열이 변할 수 있는데, 그때마다 함수의 크기가 변경되어야한다.
      • 불가능하다.
      • 함수는 caller가 누구든 간에 '스택 메모리 사용량'은 컴파일 시 고정
    • 따라서, 배열매개변수로 전달되면, 사실은 함수에서는 배열의 값을 전부 복사하는게 아닌, 배열의 시작 주소를 전달한다.
      • 따라서, 배열내에서 sizeof()를 찍으면, sizeof( int * ) 의 값이 나온다.
    • 그래서 배열의 크기, 배열 요소수는 매개변수로 전달해주자.

    void process(int nums[5]){
       ...
    }
    • 이것 또한 컴파일하면 void process(int nums[])와 동일하다.

    배열 요소 수 구하기

    1. 방법
    int values[30];
    const size_t num_vals = sizeof(values) / sizeof(values[0]);
    1. 매크로 사용 방법
    /* 함수 밖에서 */
    #define ARRAY_LENGTH(arr) (sizeof(arr) / sizeof(arr[0]))
    
    /* 사용 */
    const size_t num_vals = ARRAY_LENGTH(values);

    size_t

    • unsigned 정수형 상수 : size_t
    • _ttypedef를 했다는 힌트
      • typedef는 다른 자료형에 별칭을 붙이는 것
      • 플랫폼에 따라 다른 자료형을 쓰기 위해서 size_ttypedef한 것
        • Clang Windows
          • 64bit 운영체제: unsigned __int64 (64bit)
          • 그외 운영체제: unsigend int (32bit)

    size_t의 용도

    • 반복문 / 배열
    int int_arr[30];
    size_t i;
    
    for (i = 0; i < 30; ++i) {
        int_arr[i] = i;
    }
    • 배열 인덱스 접근

    메모리

    • 실행파일에는 데이터 + 코드 + bss 존재
      • 프로그램이 메모리에 데이터 + 코드 + bss 를 로드
      • 프로그램이 실행하면서 , 스택을 동적으로 로드
    • 운영체제가 메모리를 용도별로 나누면, 각 용도에 맞게 적절한 권한을 부여할 수 있다는 장점.
      • 권한은 r,w,x
      • cpu는 메모리에 대해 권한이 부여된 행위만 할 수 있다.

    코드

    • 실행가능한 기계 코드가 위치하는 영역으로, 텍스트 세그먼트라고도 불린다.
    • 프로그램이 동작하려면, 코드를 실행할 수 있어야하므로, 이 세그먼트에는 r,x 권한이 부여
      • 반면 w 권한은 공격자가 악의적인 코드를 삽입하기 쉬워지므로, 현대 OS에서는 w 권한 제거
    • 아래 정수 31337을 반환하는 main함수가 컴파일 되면, 554889e5b8697a0005dc3이라는 기계 코드로 변환되는데, 이 기계 코드가 코드 세그먼트에 위치한다.
    int main() { return 31337; }

    데이터

    • 컴파일 시점에 값이 정해진 전역변수전역상수
    • CPU가 이 세그먼트의 데이터를 읽을 수 있어야 하므로, r 권한 부여
    • 데이터 세그먼트는 쓰기가 가능한 세그먼트와 쓰기가 불가능한 세그먼트로 분류
      • 쓰기 가능 세그먼트: data 세그먼트
        • 전역 변수와 같이 프로그램이 실행되면서 값이 변할 수 있는 데이터
      • 쓰기가 불가능 세그먼트: rodata (readonly data) 세그먼트


    BSS

    • 컴파일 시점에 값이 정해지지 않은 전역변수
      • 프로그램이 시작될 때, 모두 0으로 초기화
    • r, w 권한 부여

    스택

    • 스택의 메모리 '크기'는 프로그램 빌드시 결정됨
      • 컴파일 옵션으로 설정 가능
      • clang windows에서 그냥 빌드하면 대략 1MB
    • 스택의 메모리 위치실행 시 결정
    • 형으로 생성한 변수는 스택에 할당
      • 스택은 빌드시 '크기'를 지정되어 있고 바로바로 이어서 할당하면 되기 때문에 힙보다 빠름
    • 프로세스가 실행될 때, 이 프로세스가 얼만큼의 스택 프레임을 사용하게 될지 미리 계산 불가능
      • OS는 프로세스를 시작할 때, 작은 크기의 스택 세그먼트를 먼저 할당하고 그때그때마다 확장한다.
    • CPU가 자유롭게 값을 읽고 쓸수 있어야하므로, r w 권한 부여

    스택 오버플로

    • 스택 크기가 1MB인데, 아래처럼 하면 스택 오버플로 발생
    int main(void)
    {
    	char buf[1024*1024];
        ...
    }
    • 정해진 스택 메모리보다 많은 양을 사용하려하는 순간, 소유하지 않은 데이터를 접근 하는 것은 매우 위험하다.
    • 위험하지만 조용히 넘어가는 경우도 있다.
      • 좋은 개발툴을 쓰면 체크해주지만.. 아닌 경우 실제 문제가 있지만 그냥 사용되는 경우도 있음

    • 형이 아닌 new(동적할당)로 생성하면 에 할당
      • 메모리 누수가능성
      • 할당 요청 시, OS가 메모리 힙영역에 빈공간을 찾아야하므로 스택보다는 시간 소요
    • 동적할당시,
      • 함수의 범위에 상관 없이 한동안 사용
      • 데이터 크기를 컴파일 도중 알 수 없어서 --> 동적할당.
        • 스택은 컴파일 시, 크기 알고 있어야함.
      • 프로그램 실행 수명 보다는 짧은 시간 사용

    전역 변수

    • 다른 소스코드 파일에서 '링크 가능'
    • 프로그램 실행 동안 공간 차지
    • 데이터 섹션에 들어간다.

    전역 변수 에러케이스

    • 에러 케이스 1
    • main.c에서 g_mob_count변수 선언이 되어있지 않아 '컴파일 에러'

    • 에러 케이스 2
    • 그래서 main.c에서 g_mob_count변수를 선언했더니, '링커 오류' 발생
      • 컴파일은 .c는 각각, 링킹은 .o를 한번에 하니까 링킹 단계에서 발견.

    extern

    • monster_repo.c에 있는 g_mob_count 전역 변수를 사용할 것이다.
      • 마치 함수처럼 컴파일 단계에선, main.cg_mob_count구멍으로 남겨둬!
      • 링킹때 구멍메꿔
    1. 방법

    1. 방법

    • 만약 여기서 extern을 빼버리면 에러케이스2와 똑같은 결과

    • 함수는 extern 붙일 수 있으나 그냥 선언하면 자동 extern

    전역 변수의 문제

    • 다른 .c 파일에서 extern 쓰면 사용 가능하고, 내 파일 안의 변수까지 바꿔버린다.
    • 파일이 2만 개나 있으면 어떤 코드가 전역 변수를 바꾸는지 알기 어렵다.
    • 다른 놈이 내 전역 변수를 못쓰게 하고 싶다!!

    static

    • 위와 같은 전역변수 + extern의 문제로.. 다른 놈이 내 전역 변수를 못쓰게 하고 싶다!!
    • static 전역변수
      • 전역 변수를 다른 파일에서 접근 못하게 함
        • 당연히, 선언한 파일 안에서는 전역변수 기능 수행
      • 정적 변수라고도 함

    지역변수

    static

    • 지역 변수에도 static 할 수 있다.

    • static + 지역 변수 시, 개념상 전역 변수
      • 처음 호출에만 초기화
      • 데이터영역에 저장
      • 지역변수처럼 접근 범위
      • 전역 변수처럼 프로그램 종료시 까지 메모리 할당

    배열

    값형 배열


    초기화

    int num[4]; /* 쓰레기 값 */
    int num[4] = { 10, 15 }; /* 10, 15, 0 ,0 */
    int num[4] = { 0, }; /* 0 으로 전부 초기화 */

    함수 매개변수 전달

    • 링크를 참고
      • 함수로 값형 배열을 전달해도, 함수에는 배열 시작 포인터가 전달되는 거라 원본 바뀐다

    2차원 배열

    함수 전달

    • 에러난다.
      이렇게 줄 것

    profile
    pllpokko@alumni.kaist.ac.kr

    0개의 댓글