C언어 이론 정리 - 02

Lee Pil Ung·2021년 12월 5일
0

C

목록 보기
2/6
post-thumbnail

📖 C언어 이론 정리 - 02

C언어 이론 정리 파트 02
자료 참조 : TCP School.com

- 변수(Variable)

변수(variable)란 데이터(data)를 저장하기 위해 프로그램에 의해 이름을 할당받은 메모리 공간을 의미한다.

즉, 변수란 데이터(data)를 저장할 수 있는 메모리 공간을 의미하며, 이렇게 저장된 값은 변경될 수 있다.

C언어에서 숫자 표현에 관련된 변수는 정수형 변수와 실수형 변수로 구분할 수 있다.

또한, 데이터가 저장된 메모리의 주소를 저장하고 처리하는 포인터 변수가 있다. 동시에 관련된 정보를 한 번에 묶어서 처리하는 사용자 정의 구조체 변수도 있다.

변수의 이름 생성 규칙

C언어에서는 변수의 이름을 비교적 자유롭게 지을 수 있다.

하지만 변수의 이름은 해당 변수에 저장될 데이터의 의미를 잘 나타내도록 짓는 것이 가장 좋다.

C언어에서 변수의 이름을 생성할 때에 반드시 지켜야 하는 규칙은 다음과 같다.

  1. 변수의 이름은 영문자(대소문자), 숫자, 언더스코어(_)로만 구성된다.

  2. 변수의 이름은 숫자로 시작될 수 없다.

  3. 변수의 이름 사이에는 공백을 포함할 수 없다.

  4. 변수의 이름으로 C언어에서 미리 정의된 키워드(keyword)는 사용할 수 없다.

🏷 변수명 생성 규칙 예시

변수의 이름이 올바른 경우변수의 이름이 잘못된 경우잘못된 이유
int tcpint t!cp*변수 이름이 영문자,숫자, _ 외에 특수문자 사용하였음
int schoolint 6school변수 이름이 숫자로 시작하였음
int tcpschoolint tcp school변수 이름 tcp와 school 사이에 공백이 있음
int int2int int변수 이름에 int라는 키워드를 사용하였음
  • C언어에서는 변수의 이름에 대소문자를 구분하므로 이 점에 주의해야 한다.

    📝 ex) int tcp 과 int Tcp 는 같은 변수가 아니다.

C언어에서 사용되는 키워드(keyword)들

키워드(keyword)는 고유한 의미를 가지는 예약어이다. C언어에서는 32개의 키워드가 있으며, 이러한 키워드들은 미국표준협회 ANSI에서 지정한 키워드들 이다.

autobeakcasecharconstcontinuedefault
dodoubleelseenumexternfloatfor
gotoifintlongregisterreturnshort
signedsizeofstaticstructswitchtypedefunion
unsigendvoidvolatilewhile

변수와 메모리 구조

변수는 기본적으로 메모리의 주소(address)를 기억하는 역할을 한다.

메모리 주소란 물리적인 메모리 공간을 서로 구분하기 위해 사용되는 일종의 식별자이다.

즉, 메모리 주소란 메모리 공간에서의 정확한 위치를 식별하기 위한 고유 주소를 의미한다.

변수를 참조할 때는 메모리의 주소를 참조하는 것이 아닌, 해당 주소에 저장된 데이터를 참조하게 된다.

따라서 변수는 데이터가 저장된 메모리의 주소뿐만 아니라, 저장된 데이터의 길이와 형태에 관한 정보도 같이 기억해야 한다.

< 메모리 상에 변수가 저장되는 방식.img>

위 그림처럼 하나의 메모리 공간에는 8개의 비트로 이루어진 1바이트의 데이터가 저장된다.

따라서 메모리의 주소 또한 1바이트씩 증가되며, 낮은 주소부터 차례대로 데이터가 저장되는 방식이다.

위의 그림에서 변수의 길이가 총 4개의 메모리 공간을 포함하므로, 해당 변수에는 4바이트의 데이터가 저장되어 있다.

이때 변수의 이름은 첫 번째 메모리 주소인 0x10만을 가리키게 된다.

따라서 길이는 4이며, 변수가 어떤 형태로 구성되는지도 알아야만 해당 변수에서 데이터를 올바르게 참조할 수 있다.

변수의 선언

C언어에서는 변수를 사용하기 전에 반드시 먼저 해당 변수를 저장하기 위한 메모리 공간을 할당받아야 한다.

이렇게 해당 변수만을 위한 메모리 공간을 할당받는 행위를 변수의 선언이라고 부른다.

만약 선언되지 않은 변수를 사용하려고 하면, C 컴파일러는 오류를 발생시킨다.

C언어에서 변수를 선언하는 방법은 다음과 같이 두 가지 방법이 있다.

  1. 변수의 선언만 하는 방법
  2. 변수의 선언과 동시에 초기화하는 방법

변수의 선언만 하는 방법

이 방법은 먼저 변수를 선언하여 메모리 공간만을 할당받고, 나중에 변수를 초기화하는 방법이다.

C언어에서 변수를 선언하는 방법은 다음과 같다.

✅ 문법

타입 변수이름;

📝 예제

int num;
...
num = 20;

위의 예제처럼 정수를 저장하기 위해 메모리 공간을 할당받으면, 반드시 해당 타입의 데이터만을 저장해야 한다.

그러지 않고 다른 타입의 데이터를 젖아할 경우 저장된 데이터에 변형 및 손실이 일어날 수 있다.

변수의 초기화란 해당 변수를 사용할 수 있도록 초기값을 설정하는 행위이다.

초기화되지 않은 변수에는 아무런 의미 없는 쓰레깃 값만이 들어가 있다.

📝 예제

int num;
printf("%d", num);

위의 예제에서 변수 num은 선언만 했을 뿐 아직 초기화되지 않았다.

하지만 printf() 함수에서 변수 num을 사용해 해당 변수에 저장된 값을 출력하려고 하고 있다.

이러한 경우 c 컴파일러는 오류를 발생시키지 않겠지만, 프로그램은 사용자가 의도하지 않은 결과를 출력할 것이다.

따라서 C언어에서는 초기화되지 않은 변수는 절대로 사용하면 안된다.

변수의 선언과 동시에 초기화하는 방법

그렇기 때문에 C언어에서 변수는 선언과 동시에 그 값을 초기화할 수 있다.

또한, 선언하고자 하는 변수들의 타입만 같다면 여러 변수를 동시에 선언할 수도 있다.

✅ 문법

  1. 타입 변수이름[, 변수이름];
  2. 타입 변수이름 = 초깃값[, 변수이름 = 초깃값];

📝 예제

int num01, num02;
double num03 = 1.23, num04 = 4.56;
  • 🚩 선언하고자 하는 변수의 타입이 서로 다르면 동시에 선언이 불가능하다.

- 상수(Constant)

상수(constant)란 변수와 마찬가지로 데이터를 저장할 수 있는 메모리 공간을 의미한다.

하지만 상수가 변수와 다른 점은 프로그램이 실행되는 동안 상수에 저장된 데이터는 변경할 수 없다는 점이다.

상수는 때문에 표현 방식에 따라 다음의 케이스들로 나뉘게 된다.

  1. 리터럴 상수(literal constant)

  2. 심볼릭 상수(symbolic constant)

리터럴 상수(literal constant)

리터럴 상수(literal constant)는 변수와 달리 데이터가 저장된 메모리 공간을 가리키는 이름을 가지고 있지 않다.

C언어에서는 적절한 메모리 공간을 할당받기 위해선 기본적으로 변수던 상수던 타입을 지녀야 한다.

리터럴 상수는 타입에 따라 크게 3가지로 구분할 수 있다.

  1. 정수형 리터럴 상수 // 123, -456과 같이 아라비아 숫자와 부호로 직접 표현되는 상수

  2. 실수형 리터럴 상수 // 3.14, -45.6과 같이 소수 부분을 가지는 아라비아 숫자로 표현되는 상수

  3. 문자형 리터럴 상수 // 'a', 'Z'와 같이 따옴표('')로 감싸진 문자로 표현되는 상수

심볼릭 상수(symbolic constant)

심볼릭 상수는 변수와 마찬가지로 이름을 지니는 상수이다.

그렇기 때문에 심볼릭 상수는 반드시 선언과 동시에 초기화되어야 한다.

심볼릭 상수는 const 키워드를 사용하거나, 매크로를 이용하여 선언할 수 있다.

📝 예제

const int MAX = 10; // const 키워드를 이용한 심볼릭 상수

#define MAX 10;     // #define 선행처리 지시자를 이용한 심볼릭 상수

- 기본 타입

C언어에서 타입(data tpye)이란 해당 데이터가 메모리에 어떻게 저장되고, 프로그램에서 어떻게 처리되어야 하는지를 명시적으로 알려주는 역할을 한다.

따라서 C언어는 여러 형태의 타입을 미리 작성하여 제공하는데 이들을 기본 타입이라고 부른다.

기본타입은 크게 정수형, 실수형, 문자형 타입 3가지로 나눌 수 있다.

정수형 타입

C언어에서 정수란 부호를 가지고 있고, 소수 부분이 없는 수를 의미한다.

정수형 데이터에 unsigned 키워드를 추가하면, 부호를 나타내는 최상위 비트(MSB = Most Significant Bit)까지도 크기를 나타내는 데 사용할 수 있다.

  • 🚩 최상위 비트(MSB, Most Significant Bit)란?

      최상위 비트(MSB, Most Significant Bit)란 1바이트를 구성하는 8개의 비트 중 최고값을 갖는 비트를 의미한다.

위의 말이 무슨 말이냐 하면 char와 int의 signed 정수형 변수에서는 최상위 비트(MSB)가 부호 비트이다.

최상위 비트가 1이면 음수이고 0이면 양수이다. 그러나 unsigned을 사용하면 음수를 사용하지 않겠다는 의미 이므로 이 부호 비트까지 크기를 나타내는 데 이용할 수 있다는 의미이다.

그렇기 때문에 이러한 unsigned 정수는 음수를 표현할 수는 없게 되지만, 0을 포함한 양의 정수는 두 배 더 많이 표현할 수 있게 된다.

음의 정수까지도 표현할 수 있는 signed 키워드는 모든 타입에서 기본적으로 생략하여 사용할 수 있다.

정수형 타입할당되는 메모리의 크기데이터의 표현 범위
(signed) short2 바이트- 32,768 ~ 32,767
unsigned short2 바이트- 0 ~ 65,535
(signed) int4 바이트- 2,147,483,648 ~ 2,147,483,647
unsigned int4 바이트- 0 ~ 4,294,967,296
(signed) long4 바이트- 2,147,483,648 ~ 2,147,483,647
unsigned long4 바이트- 0 ~ 4,294,967,296

정수형 데이터의 타입을 결정할 때에는 반드시 내가 사용하고자 하는 데이터의 최대 크기를 고려해야 한다.

해당 타입이 표현할 수 있는 벙뮈를 벗어난 데이터를 저장하면, 오버플로우가 발생해 값이 전혀 다르게 될 수 있기 때문이다.

오버플로우(overflow)란 ?

오버플로우(overflow)란 해당 타입이 표현할 수 있는 최대 범위보다 큰 수를 저장할 때 발생하는 현상을 가리킨다.

오버플로우가 발생하면 최상위 비트(MSB)를 벗어난 데이터가 인접 비트를 덮어쓰므로, 잘못된 결과를 얻을 수 있다.

반대의 경우로 언더플로우(underflow)가 있는데 이는 해당 타입이 표현할 수 있는 최소 범위보다 작은 수를 저장할 때 발생하는 현상을 가리킨다.

📝 예제

int num = 2147483647; // int형 타입이 저장할 수 있는 최댓값인 231 - 1
printf("변수 num에 저장된 값은 %d입니다.\n", num);

num = 2147483648;     // int형 타입이 저장할 수 없는 숫자인 231
printf("변수 num에 저장된 값은 %d입니다.\n", num);
// 실행결과
변수 num에 저장된 값은 2147483647입니다.

변수 num에 저장된 값은 -2147483648입니다.

두 번째 실행 결과를 살펴보면, 변수 num에 양수를 대입했지만 음수로 저장된 것을 확인할 수 있다.

이처럼 오버플로우가 발생하면 전혀 예상치 못한 결과를 얻을 수 있으므로, 데이터를 저장할 때는 언제나 해당 데이터 타입의 최대 크기까지 고려해야 한다.

실수형 타입

C언어에서 실수란 소수부나 지수가 있는 수를 가리키며, 정수보다 훨씬 더 넓은 표현 범위를 가진다.

하지만 컴퓨터에서 실수를 표현하는 방식은 반드시 오차가 발생하는 기술적 한계를 지닌다.

이러한 실수형 데이터의 오차는 C언어뿐만 아니라 모든 프로그래밍 언어에서 발생하는 공통된 문제이다.

실수형 타입할당되는 메모리의 크기데이터의 표현 범위
float4 바이트(3.4 X 10-38) ~ (3.4 X 1038)
double8 바이트(1.7 X 10-308) ~ (1.7 X 10308)
long doubledouble형과 동일함double형과 동일함

📝 예제

float num01 = 3.1415926535897932;  // float 타입의 유효 자릿수는 소수점 6자리
printf("변수 pi에 저장된 값은 %.20f입니다.\n", num01);

double num02 = 3.1415926535897932; // double 타입의 유효 자릿수는 소수점 16자리
printf("변수 pi에 저장된 값은 %.20f입니다.\n", num02);
//실행 결과
변수 num01에 저장된 값은 3.14159274101257324219입니다.

변수 num02에 저장된 값은 3.14159265358979311600입니다.

위의 예제에서 변수 num01에는 소수점 6자리까지만 정확한 값이 저장되어 있고, 소수점 7자리부터는 틀린 값이 저장되어 있다.

또한, 변수 num02에는 소수점 15자리까지만 정확한 값이 저장되어 있고, 소수점 16자리부터는 틀린 값이 저장되어 있는 것을 확인할 수 있다.

이처럼 실수형 데이터의 타입을 결정할 때는 표현 범위 이외에도 유효 자릿수를 반드시 고려해야 한다.

실수형 타입지수의 길이가수의 길이유효 자릿수
float8 비트23 비트소수 부분6자리까지 오차없이 표현할 수 있음.
double11 비트52 비트소수 부분15자리까지 오차없이 표현할 수 있음.

과거에는 실수를 표현할 때 float형을 많이 사용했었다고 하는데 요근래에는 하드웨어의 발달로 인한 메모리 공간의 증가로 현재에는 double형을 가장 많이 사용한다.

문자형 타입

C언어에서 문자형 데이터란 문자 하나를 표현할 수 있는 타입을 의미한다.

컴퓨터는 2진수밖에 인식하지 못하므로, 문자도 숫자로 표현해야 컴퓨터가 인식할 수 있다.

이러한 약속 중에서 가장 많이 사용되는 것이 바로 아스키코드(ASCII)이다.

아스키코드(ASCII)는 영문 대소문자를 사용하는 7비트의 문자 인코딩 방식이다.

아스키코드는 문자를 7비트로 표현하므로, 총 128개의 문자를 표현할 수 있다.

아스키코드(ASCII)의 구성

아스키코드의 구성은 다음과 같다.

  • 출력할 수 없는 33개의 문자

  • 출력할 수 있는 52개의 영문 대소문자, 10개의 숫자, 32개의 특수 문자와 1개의 공백 문자

문자형 타입할당되는 메모리의 크기데이터의 표현 범위
(signed) char1 바이트27 ~ 2-7
unsigned char2 바이트0 ~ 2-8

다음 예제는 char형 변수에 저장된 문자를 여러 서식 지정자를 사용해 출력하는 예제이다.

📝 예제

char ch = 'a';
printf("변수 ch에 저장된 값은 %c입니다.\n", ch);
//변수 ch에 저장된 값은 a입니다.
printf("변수 ch에 저장된 값은 %d입니다.\n", ch);
//변수 ch에 저장된 값은 97입니다.

위의 예제에서 서식 지정자 %c를 사용하여 출력한 결과는 제대로 문자 'a'가 출력된다.

하지만 서식 지정자 %d를 사용하여 출력한 결과는 97이라는 숫자로 출력된다.

즉, 이것은 문자가 C++ 내부적으로는 아스키코드에 해당하는 숫자로 저장되어 있음을 보여준다.

- 타입 변환(Type conversion)

C언어에서 다른 타입끼리의 연산은 우선 피연산자들을 모두 같은 타입으로 만든 후에 수행된다.

그렇기 때문에 서로 다른 타입을 같은 타입으로 바꿔줘야 한다. 이러한 행위를 타입 변환(Type conversion)이라 한다.

일반적으로 표현 범위가 좁은 타입에서 표현 범위가 더 넓은 타입으로의 타입 변환은 큰 문제가 되지 않는다.

하지만 반대의 경우인 표현 범위가 좁은 타입으로의 타입 변환에서는 데이터의 손실이 발생한다.

타입 변환은 크게 다음과 같이 두 가지 방식으로 나눌 수 있다.

  1. 묵시적 타입 변환(자동 타입 변환)

  2. 명시적 타입 변환(강제 타입 변환)

묵시적 타입 변환(자동 타입 변환, implicit type conversion)

묵시적 타입 변환은 대입 연산이나 산술 연산에서 C 컴파일러가 자동으로 실행해주는 타입 변환을 말한다.

C언어에서는 대입 연산 시 연산자의 오른쪽에 존재하는 데이터의 타입이 연산자의 왼쪽에 존재하는 데이터의 타입으로 묵시적 타입 변환이 진행된다.

또한, 산술 연산에서는 데이터의 손실이 최소화되는 방향으로 묵시적 타입 변환이 진행된다.

📝 예제

아래 예제들은 대입 연산에서 일어나는 묵시적 타입 변환의 사례들이다.

char ch = 200;
int num01 = 3.14;
double num02 = 5;

printf("   ch에 저장된 값은 %d입니다.\n", ch);
// ch에 저장된 값은 -56입니다.

printf("num01에 저장된 값은 %d입니다.\n", num01);
// num01에 저장된 값은 3입니다.

printf("num02에 저장된 값은 %f입니다.\n", num02);
// num02에 저장된 값은 5.000000입니다.

위의 예제에서는 char형 변수에 char형 변수가 표현할 수 있는 범위를 넘는 데이터를 저장한다.

따라서 전달된 데이터의 상위 비트가 자동으로 삭제되어 데이터의 손실이 발생한다.

또한, int형 변수에 실수를 저장했기 때문에 소수 부분이 자동으로 삭제되어 데이터의 손실이 발생한다.

하지만 double형 변수에 int형 데이터를 저장하는 것은 데이터가 double형으로 자동 타입 변환되지만, 데이터의 손실은 발생하지 않는다.

📝 예제

다음 예제는 산술 연산에서 일어나는 묵시적 타입 변환을 보여준다.

double result01 = 5 + 3.14;
double result02 = 5.0f + 3.14;

printf("result01에 저장된 값은 %f입니다.\n", result01);
// result01에 저장된 값은 8.140000입니다.

printf("result02에 저장된 값은 %f입니다.\n", result02);
// result02에 저장된 값은 8.140000입니다.

위의 예제에서 첫 번째 연산은 int형 데이터와 double형 데이터의 산술 연산이다.

따라서 데이터의 손실이 최소화되도록 int형 데이터가 double형 데이터로 자동 타입 변환된다.

두 번째 연산은 float형 데이터와 double형 데이터의 산술 연산이다.

위와 마찬가지로 데이터의 손실이 최소화되도록 float형 데이터가 double형 데이터로 자동 타입 변환된다.

이렇게 컴파일러가 자동으로 수행하는 타입 변환은 언제나 데이터의 손실이 최소화되는 방향으로 이루어집니다.

따라서 C 컴파일러는 다음과 같은 순서대로 자동 타입 변환을 수행하게 된다.

✅ 컴파일러의 변환 순서

char 형 → short 형 → int 형 → long 형 → float 형 → double 형 → long double 형

명시적 타입 변환(강제 타입 변환, explicit type conversion)

명시적 타입 변환은 사용자가 타입 캐스트(type cast) 연산자를 사용하여 강제적으로 수행하는 타입 변환을 가리킨다.

변환하고자 하는 데이터의 앞에 괄호(())를 추가하고, 그 안에 변환할 타입을 적으면 된다.

C언어에서는 이 괄호(())를 타입 캐스트(type cast) 연산자라고 한다.

📝 예제

int num01 = 1;
int num02 = 4;

double result01 = num01 / num02;
double result02 = (double)num01 / num02;

printf("result01에 저장된 값은 %f입니다.\n", result01);
// result01에 저장된 값은 0.000000입니다.

printf("result02에 저장된 값은 %f입니다.\n", result02);
//  result02에 저장된 값은 0.250000입니다.

위의 예제에서 첫 번째 연산의 결괏값은 0.000000으로 출력된다.

그 이유는 산술 연산에 대한 결괏값의 타입은 피연산자의 타입과 언제나 일치하기 때문이다.

즉 int형 데이터끼리의 산술 연산에 대한 결괏값은 언제나 int형 데이터로 나온다.

따라서 두 번째 연산에서처럼 하나의 피연산자를 명시적으로 double형으로 지정해야만 정확한 결괏값을 얻을 수 있다.

profile
Frontend SoftWare Engineer(2022.06.27 ~)

0개의 댓글