brunch

매거진 Sinclair

You can make anything
by writing

C.S.Lewis

by Sinclair Feb 05. 2016

C의 자료형과 변수

너는 내 타입 II




C의 사용자 정의 자료형: typedef  



C언어는 어렵고 복잡한(주로 확장 자료형) 자료형을 사용자 정의 자료형으로 간단하게 바꿔서 사용할 수 있도록 해줍니다. 새로운 타입선언은 다음과 같습니다.  


typedef  type 새로운타입명 ;  


type 변수명(들);

이렇게 선언하면 변수 선언이었던 것이 type 앞에 typedef를 붙이면 변수명이 새로운 type이 되는 것 입니다. 새로운 타입 선언이 어렵다면 일반 변수 선언을 한 후에 앞에 typedef를 붙이고 변수명을 타입처럼 사용하면 됩니다.


예를 들면  


int * ( * functionPointer [10] ) ( int ,int ) ;  


실제로 functionPointer라는 함수 포인터 배열 변수를 선언한 것입니다. 이것을  


typedef  int * ( * functionPointer [10] ) ( int ,int ) ;  


라고 선언하게 되면 functionPointer는 변수가 아니라 새로운 타입이 됩니다. 갑지기 그냥 포인터도 아니고 함수의 포인터 배열이 등장하니 많이 어렵나요? 좀 더 자세한 내용은 Advanced Pointer를 얘기하게 될 다른 글에서 자세히 설명할 것입니다. 참 네, 예를 들어도 어디서 이 딴 것을 들다니.. 그럼 쉽게 다른 예를 보면,  


int * iPointer ;  


라고 선언하면 iPointer 라는 포인터 변수가 선언됩니다. 하지만,  


typedef  int * iPointer ;  


라고 하게 되면 변수였던 iPointer가 새로운 타입이 됩니다. 그러면 앞으로 int *라는 타입 대신에 iPointer라는 타입을 사용할 수 있습니다. 그냥 차라리 int * 로 사용하는 게 나을까요? 음, 그건… 그때 그때 달라요~   










이것은 일반적으로 프로그램이 메모리에 올라가 프로세스가 될 때의 메모리 그림입니다. 물론 OS마다 다르기도 하지만 기본적인 구조는 다음과 같습니다.


일반적으로 메모리 높은번지 (숫자가 큰쪽)

일반적으로 메모리 낮은번지 (숫자 작은쪽)



Stack Segment

모든 자동변수가 여기에 오며 사용 전후 초기화하지않습니다. 빈 공간 없이 순차적으로 사용되며 LIFO(Last Input First Output)구조의 메모리 공간입니다. 일반적으로 스택은 아래로 증가 합니다.


Heap(근거리)

malloc(), calloc(), realloc() 등 동적 할당 함수로 할당되는 메모리 공간으로 동적할당 된 메모리는 반드시 free()함수로 해제해야 합니다.


Data Segment

일반적으로 전역변수, static변수, 상수 등이 할당되는 메모리 공간으로 프로그램이 메모리에 존재하는 동안 그 값이 계속 유지됩니다.


Code Segment

실제 실행되는 기계어 명령코드들이 존재하는 메모리 공간



변수를 선언할 때 타입 앞에는 다음 제약어들이 올 수 있습니다. 각 제약어는 종류별로 한가지만 올 수 있으며 세 가지 종류를 어떠한 순서로 섞어 사용해도 의미가 변하지는 않습니다.                                 


기억 부류지정어 : auto(생략), register, static, extern


타입 한정어 : const, volatile


부호 지정어 : signed(생략), unsigned




기억 부류 지정어들  

auto: 자동변수로 메모리 stack segment에 저장됩니다. 반드시 {} 블록 안에서 선언해야 합니다. 접근 범위와 라이프 타임도 {} 블록과 동일하며 보통은 생략하는 것이 일반적입니다. 많은 사람들이 지역변수라고 부르지만 사실 그것은 잘못된 호칭이며 자동변수라고 부르는 것이 정확한 표현입니다.

register: CPU 내부의 레지스터에 저장 되는 것을 제외하면 auto와 동일합니다. 하지만 다른 지정어와 달리 register는 반드시 레지스터에 저장 된다는 것을 보장하지 못합니다. 레지스터의 개수는 한정되어 있으며 대부분 CPU가 사용하고 있습니다. 때문에 레지스터를 사용하게 해달라고 요청하는 것이라 생각하면 됩니다. 일부 컴파일러는 최적화 기법을 통해 register라고 따로 선언하지 않아도 적절히 레지스터에 넣어 사용한다고 합니다. register변수는 메모리가 아니기 때문에 주소 값을 갖지 않습니다. 때문에 주소 단항 연산자인 &를 사용할 수 없습니다. 주소 값이 없다는 것이지 주소 값을 레지스터에 담을 수 없다는 것은 아닙니다.
register int * iPointer ; 는 충분히 가능하지만 &iPointer는 안됩니다.   


2017년 현재 C++17 표준의 경우 

register - automatic storage duration. Also hints to the compiler to place the object in the processor's register. (deprecated) 라고 하고 있습니다. C가 아니라 C++입니다.



#include <stdio.h>  

/*

* copyleft (l) 2006 - 2017 programmed by Sinclair

*/   

int main() {

    int first , second , third ;  

    first = 10 ;     second = 20 ;     third = 30 ;  

    printf("1: %d %d %d\n" , first , second , third ) ;  

    {

        int second = 100 ;

        first = 50 ;

        printf("2: %d %d %d\n" , first , second , third ) ;  

        {

            int third = 40 ;

            first = 50 ;

            second = 200 ;  

            printf("3: %d %d %d\n" , first , second , third ) ;

        } // end inner block  

        printf("4: %d %d %d\n" , first , second , third ) ;  

    } // end outer block  

    printf("5: %d %d %d\n" , first , second , third ) ;  

    return 0 ;  

} // end main()    




static(internal): 일반적으로 자동변수는 블록에 따라 관리되는 스택 세그먼트에 올라갑니다. 블록이 끝이 나면 더 이상 사용할 수 없습니다. 가끔 사용할 수 있는 것처럼 보이기도 합니다. 하지만 그것은 스택을 사용 후에 청소하지 않는다는 이유 때문이지 값을 그대로 보장 하기 때문이 아닙니다. 특히 배열을 리턴 하고 난 후에 그 값을 확인해 보면 엉뚱한 값이 들어 있는 것을 확인 할 수 있습니다. 이것은 배열 이야기를 하면서 함께 자세히 알아보겠습니다. 그렇기 때문에 굳이 배열을 리턴 하려면 반드시 배열을 static으로 선언해야 합니다. static으로 선언하면 스택이 아닌 데이터 세그먼트에 자리하게 되고 프로그램이 실행되는 동안 그 값이 계속 유지 됩니다. 하지만 값이 유지 된다는 것이지 변수의 접근 범위까지 확장 되지는 않습니다. 물론 주소 값으로 직접 접근한다면 이야기는 달라집니다.  


static(external): 전역변수는 일반적으로 데이터 세그먼트에 자리하며 프로그램에서는 함수 외부에 선언한 변수를 말합니다. 전역 변수는 가급적 적게 사용하는 것이 좋습니다. 간혹 보면 전역 변수를 마구마구 남발하는 것을 많이 보게 됩니다. 함수의 인자를 잘못 사용하니 값이 변하질 않고 그러다 보니 전역변수로 빼버립니다. 그러다 보면 아무 함수에서 불러서 값이 변경됩니다. 값이 어디서 변하는지 알기 힘들어 집니다. 뿐만 아니라 외부 변수로 확장이 되면 디버깅은 더욱 어려워집니다. 매우 위험하죠? 그렇기 때문에 전역 변수는 적절히 사용하셔야 합니다. 제가 분명히 static변수도 데이터 세그먼트에 간다고 했습니다. 그러면 원래 데이터 세그먼트에 있는 것을 static으로 선언할 필요가 있을까요? 매우 중요한 전역 변수가 언제 외부 변수로 확장이 될지 모르는 위험에 노출되어 있다면 바로 그 때 static으로 선언하여 내부 연결로 사용하는 것입니다. static 전역변수는 외부변수로 확장되지 않습니다. 작성하고 있는 파일 내에서만 접근 가능한 일종의 private기능을 하게 됩니다. static 함수도 마찬가지입니다. 외부 다른 파일에서는 호출할 수 없으며 오직 같은 파일 범위에서만 호출 가능합니다.  


extern: 일반적으로 extern은 두 가지 의미를 포함합니다. 일반 전역변수의 의미와 변수가 외부 다른 파일에 선언되어 있다는 의미입니다. 대부분의 컴파일러에 구현된 것은 일반 전역변수의 경우는 생략하고 외부 선언된 변수 임을 알리고자 할 때는 extern이라 선언하고 값을 대입하지 않는 형태입니다.

                                

표준 C에서의 extern

int first ; // 임시 정의,외부에서 선언하여 사용가능

int second = 0 ; // 정의, 외부에서 선언하여 사용가능

extern int third ; // 선언, 다른 파일에 선언된 것을 링크 예정

extern int forth = 0 ; // 정의, 외부에서 선언하여 사용가능

// 하지만 대부분의 unix계열은 에러로 표현


int function() ; // 함수의 프로토타입

extern int function2(); // 외부 함수의 프로토타입



int function() {

    extern int fifth ; // 함수 아래 영역에 선언된 변수사용

    // do something

    function2() ; // 위에 선언된 외부 함수 호출

    return 0 ;

} // end function()


int fifth ;

int function1() {

    // do something

    fifth = 100 ; // 그냥 사용 가능

    return 0 ;

} // end function1()



그러면 이제 우리가 지금까지 함께 공부한 기억 부류 지정어들을 정리하면 다음과 같습니다.












#Sinclair #씽클레어 #싱클레어 #씽클레어도씨 #씨언어 #씨프로그래밍  #C언어 #Cprogramming #C_Programming #C #Programming #Clanguage #C_Language




매거진의 이전글 C의 자료형과 변수
작품 선택
키워드 선택 0 / 3 0
댓글여부
afliean
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari