본문 바로가기

Programming/C언어 초급

C언어 초급) 02.변수 : 01. 변수의 정의

들어가며...

개인적으로 프로그래밍 언어를 처음 접할 때 반드시 살펴보는 부분이 있는데 바로 변수의 종류(타입)와 크기입니다. 물론 프로그래밍 언어마다 표현하는 문법이 비슷하기도 하고 매우 낯선 경우도 많아 개발 및 공부를 하면서 어쩔 수 없이 학습하고 익숙해져야 하는 부분이지만 변수는 대부분 비슷한 듯 차이점이 있어 중요하게 생각하는 부분 중 하나입니다.

 

개발 초심자들이 많이 하는 실수중에 하나가 변수의 특성과 크기를 고려하지 않고 사용하거나 변수를 선언하고 초기화를 하지 않아 예상치 못한 결과를 도출하기도 합니다.

 

이번장에서는 이러한 변수에 대해서 살펴보도록 하겠습니다.

 

✳︎ 생각해보기

변수의 종류와 크기는 스크립트 언어를 제외한 대부분의 언어에서 비슷하게 사용됩니다. 따라서 C언어의 변수에 대해 잘 이해하신다면 다른언어를 공부할 때 변수의 종류와 크기만 알아두시면 나머지 개념은 모두 동일하다고 생각하시면 됩니다.

 

  • Table of Contents
    • 변수란?
    • 변수와 메모리
    • 변수의 타입
    • 변수의 선언 및 규칙

변수란?

변수는 특정한 값을 담기위한 그릇이라 생각할 수 있습니다. 변수를 선언하면 변수명과 매핑되는 메모리 주소를 할당하고 해당 메모리 주소가 가리키는 영역에 실제 값이 저장됩니다.

 

변수와 메모리

C언어는 포인터가 전부라고 할 정도로 포인터라는 개념을 이해하지 못하면 C언어를 제대로 사용할 수가 없습니다. 이번 장에서 포인터에 대해 설명하려는 것은 아니고 포인터의 개념을 이해하려면 결국은 내가 정의한 변수가 메모리의 어디에 어떻게 저장되는 지를 알아야 합니다.

이 부분이 다른 고급언어와 C언어의 차별점이면서 C언어가 어렵다고 느끼게 하는 부분이 아닐까 싶습니다.

 

C언어를 능숙하게 다루게 되는 시점이면 내가 선언한 변수가 메모리의 어딘가에 얼마의 크기로 차지하고 할당되는 지 머리속에 그려지고 포인터를 이용하여 메모리주소를 이리저리 옮기다니면서 그 부분에 어떤 값이 있는 지도 대략 짐작하게 됩니다.

거짓말 같지만 이정도로 C언어에서는 메모리의 관리가 매우 중요하다는 의미입니다. 메모리의 주소를 직접 옮겨다니며 값을 제어할 수 있기 때문에 메모리의 개념을 이해하지 못하면 엉뚱한 메모리 주소의 값을 변경하여 원하지 않은 결과가 나오거나 소위 프로그램이 뻗어버리기 때문입니다.

 

그럼 위에 추상적으로 표현한 변수의 선언 및 값 할당에 대한 부분을 실제 메모리에 어떻게 표현되고 값이 할당되는 지 살펴보겠습니다.

(제가 사용하는 PC가 Mac인 관계로 xcode를 사용한 예제임을 참조하시기 바랍니다.)

 

● 변수의 선언

#include <stdio.h>

int main(int argc, const char * argv[]) {
    // insert code here...
    char a = 'a';
    int b = 0xaa;
    char c = 'b';

    printf("a's address = 0x%02x, size = %lu\n", &a, sizeof(a));
    printf("b's address = 0x%02x, size = %lu\n", &b, sizeof(b));
    printf("c's address = 0x%02x, size = %lu\n", &c, sizeof(c));
    
    return 0;
}

변수를 3개 선언하고 각각의 주소번지와 크기를 출력하는 간단한 예제입니다.

 

● 출력결과

a's address = 0xefbff52f, size = 1
b's address = 0xefbff528, size = 4
c's address = 0xefbff527, size = 1

아래에서 살펴보겠으나 변수 a, c는 char 변수이므로 크기가 1이고, 변수 b는 정수형 변수이므로 크기가 4로 출력되었으며 그 크기에 따라 메모리의 주소값이 변경된 것을 확인할 수 있습니다.

 

위의 값들이 실제 메모리에 어떻게 할당되어 있는지 살펴보면 아래와 같습니다.

 

● 실제 메모리에 적재된 모습

메모리의 내용을 살펴볼 땐 주로 16진수로 나타냅니다. 그리고 문자의 경우 아스키값으로 대체되어 저장되게 됩니다.

컴퓨터는 0과 1밖에 모른다는 말이 어떤 의미인지 아시겠는지요? 위의 메모리 내용도 처음 접하는 분들은 헷갈리실 수 있겠으나 위의 한바이트 한바이트는 결국 2진수로 변경하여 표현하실 수 있어야 합니다.

 

이번 포스팅에서 2진수, 10진수, 16진수간의 변환에 대한 내용을 언급하진 않겠으나 인터넷을 검색하시어 3개의 진법간 어떻게 변환되는 지 잘 알아두셔야 추후에 버그나 디버깅을 하실 때 메모리의 내용을 살펴보면서 값들을 확인 하실 수 있게 됩니다.

해당 부분은 추후 기회가 된다면 별도의 포스트로 올려보도록 하겠습니다.

 

✳︎ 생각해보기

위의 메모리 구조를 보니 약간 이상한 부분이 있습니다. 변수의 선언 순서는 a, b, c 인데 메모리에는 거꾸로 저장되어 있음을 알 수 있습니다. 이는 byte order가 little endian 이기 때문입니다.

이 또한 설명하자면 길어지기 때문에 여기서는 간략하게 요약만 하고 넘어가도록 하겠습니다.

 

  • byte order : 메모리에 적재되는 순서를 의미. 여러 표현이 있으나 여기서는 big endian, little endian을 사용토록 하겠습니다.
  • little endian : 위의 그림처럼 메모리에 거꾸로 할당되어 표현되는 방식
  • big endian : 위의 그림과 반대로 표현되며 사람이 이해하기 편함. 위의 변수를 big endian방식으로 표현하면 "61 00 00 00 AA 62" 될 것입니다.

메모리에 저장될 때 문자들은 아스키값으로 대체된다고 하였습니다. 소문자 'a'의 경우 아스키값으로 0x61이며 위의 그림에서와 같이 맞게 저장된 것을 확인할 수 있습니다.

 

● Ascii table

변수의 타입

변수의 타입별로 큰 범주로 분류하면 다음과 같습니다.

 

구분 타입 설명
기본형 정수형 int, long 정수
문자형 char 문자
실수형 float, double 실수
열거형 enum 나열형
void형 void 타입이 정해지지 않은 자료형
유도형 배열 int a[10];, char b[20]; 같은 타입의 집합
구조체 struct user {
    int age;
    char name[20];
}
다른 타입의 집합
공용체   메모리를 공유하는 집합
포인터 int *a; 대상체의 번지를 가리키는 타입

 

변수의 선언 및 규칙

기본형(정수, 실수, 문자 등)을 기준으로 변수를 선언하는 방법에 대해서 간략하게 정리하면 아래와 같습니다.

 

● 코드

//
//  main.c
//  variables
//
//  Created by leo on 2020/05/22.
//  Copyright © 2020 leo. All rights reserved.
//

#include <stdio.h>

int main(int argc, const char * argv[]) {
    //< 초기화 없음
    int no_init;
    //< 초기화
    int init = 10;
    //< 여러개 동시에 선언
    int a, b = 3, c, d;
    
    printf("초기화 없음 : %d\n", no_init);
    printf("초기화 : %d\n", init);
    printf("여러개 선언 : a = %d, b = %d, c = %d, d = %d\n", a, b, c, d);
    
    return 0;
}

 

● 결과확인

 

 

✳︎ 생각해보기

주의할 점은 초기값 없이 변수를 선언했을 경우 다른 언어처럼 기본적으로 0으로 초기화가 안된다는 점입니다. 위의 예제와 같이 no_init값은 출력결과가 0이여서 초기값이 0으로 되는 것처럼 보이지만 아래 a, b, c, d 를 살펴보면 a, c는 임의 값이 출력되어 있는 것을 알 수 있습니다. 따라서, C언어 뿐만 아니라 나머지 언어에서도 변수 선언 시 가능하다면 반드시 초기값을 할당하는 습관을 들이는 것이 좋습니다.

 

마무리...

모든 언어 대부분이 프로그램의 구조와 변수의 종류까지는 이론적으로 학습을 하는 것이 매우 중요합니다. 이 외 기능적인 부분은 문법의 차이는 조금씩 다르지만 목적이 확실하기 때문에 기본적인 개념은 동일하다고 생각하시면 됩니다. (물론 객체지향 언어, 스크립트 언어, 모바일 또는 웹 어플리케이션 처럼 순차적이지 않고 이벤트성 언어들만의 특징은 또 다른 문제지만...)

 

다음 몇 포스트에 걸쳐 변수에 대해 마저 설명하고 나머지 문법(제어문, 연산 등등)들은 예제 위주로 설명할 예정입니다.

 

 


U2ful은 입니다. @U2ful Corp.