거북이와 개구리의 끄적끄적

2일차_C언어(상수, 변수, 함수, 표준출력함수) 본문

공부/팁스

2일차_C언어(상수, 변수, 함수, 표준출력함수)

거북이가개굴개굴 2018. 7. 2. 17:09


이 글은 Tips 2일차 강좌를 듣고 정리한 글입니다.

틀린 내용, 보충할 내용, 궁금한 내용 있으시면 댓글 달아주시면 감사하겠습니다~

상수와 변수 & 함수 & 표준출력함수

오늘은 상수와 변수, 함수, 표준출력함수에 대해 정리해보겠습니다.



가.상수와변수

상수를 c언어에서 어떻게 사용하는지, 또한 변수란 무엇이고 어떻게 사용하는지, 2진법과 16진법 사이의 변환에 대해서 설명하겠습니다.


1.상수란?
상수는 왜 필요할까?
우리가 하려는 건 컴파일러를 통해 소스파일을 기계어로 바꾸는 것.
그리고 이런 컴파일러의 규칙에 맞게 소스파일을 자성하는게 C언어 문법임.
그럼 컴파일러 기준에서 봤을 때 소스코드를 보고 이게 뭔지 판단의 기준이 필요.
그래서 상수라는 문법이 있는 것. ‘A’라 적으면 컴파일러가 이걸 보고 65라고 판단 가능.


<숫자형 상수>
정수,실수 : 8진법, 10진법, 16진법
16
진법은 왜 쓸까?
숫자형 상수에는 굳이 2진법이 필요가 없다.
2
진법으로 int형 자료형 표현하려면 32비트이므로 숫자 32개를 적어야 함.
int
2개끼리 더하려면 숫자만 64개 적어야 됨. -> 비효율적.
차라리 16진법을 쓰자. 16진법과 2진법은 쉽게 변환할 수 있고, 실제로 사용하기에도 효율적임.
16
진법 2자리면 1바이트임.
10
진법은 왜 쓸까?
사람들이 소스파일 작성하기에 편리해야 함.


<문자형 상수>
영문자’, ‘숫자형’, ‘특수문자와 같이 ‘’를 이용. single quotation
ex) ‘A’
라고 적으면 c컴파일러는 int 65로 취급. cpp컴파일러는 자료형에 엄격하므로 char65로 취급.


<문자열형 상수>
즐거운 하루와 같이 “”를 이용. double quotation
ex) ”abc”
라고 적으며, 문자열 상수는 주소값에 대한 개념이 나오고 이는 포인터를 배워야 함. 추후에 포인터 배우고 다시 설명.


2.변수란?
변수는 자료형과 합쳐질 때 의미가 있음.
선언할 때 자료형 쓰고 이후에 눈에는 안보이지만 계속 자료형이 변수이름에 붙어 다님
메모리를 사용하는데 얼마나 사용할지 선언해야 하므로 자료형을 적고 그 메모리의 이름을 정하는 것.


<변수 선언>
네이밍은 중요함. 4~6자 내외로.
int n = 0;
이라 하면 내가 쓸 공간은 4바이트고, n이라고 쓸 것.
int
는 자료형, n은 변수이름, ;는 구분자, n = 0은 초기화.
한줄에 ,로 여러 개 선언 가능.


<초기화란?>
int i = 0;이란건 기계어로 번역되면 int i; i = 0; 두가지 operation으로 나뉘어짐.
, 초기화는 딱히 연산에서의 이득은 없음.
그러므로 이런 습관적인 초기화는 의미없는 operation 낭비가 될 수 있다.
따라서 개발자가 변수를 사용하기 전에 필요에 의해 초기화해주는게 좋다.


<2진법과 16진법사이의 변환>
c언어에서는 2진수 표현방법이 없기 때문에 보통 16진가 2진수 4개를 이용해 표현되므로 16진수와 2진수사이의 변환을 한다.


<비표준이란?>
비표준이란 말그대로 표준이 아닌 것을 의미함.
ex) _int16 :
이놈은 그나마 조금 호환됨.
   __int16 :
이놈은 더 호환이 안됨.
그럼 호환성이 안좋음에도 불구하고 비표준은 왜 있는걸까?
비표준이 꼭 안좋은건 아님. 좀 더 확실하게 쓸 수 있음.
자기의 환경이 변하지 않는다면 비표준도 나름 좋은 선택.
또한 함수나 변수 이름 명명할때 아무생각없이 _붙이지 말 것. 비표준이라는 뜻임.


 


나.함수

함수란 명령어를 그룹 짓는 문법으로 함수의 필요성, 함수의 정의와 호출, 함수의 원형에 대해 설명해보겠습니다.


1. 함수의 필요성
먼저 함수라는 것은 1일차에서 설명한 명령어를 괄호로 묶어 하나의 이름으로 명명하는 문법입니다. ‘물을 마신다라는 예시를 들어 설명해보겠습니다.
물을 마신다라는 작업은 굉장히 많은 내용이 생략되어 있는데 이를 자세히 풀어 쓰면 '물을 담을 수 있는 용기가 있는 곳으로 간 후, 그곳에서 용기를 가져오고(물컵), 물을 있는 곳으로 가서(정수기) 용기에 물을 담아서 그 물을 마신다'와 같이 쓸 수 있는데 이를 C언어처럼 표현하면 아래와 같은 형태가 됩니다.


<main함수와 분리하지 않은 형태>
int main(){
   
물을 담을 수 있는 용기가 있는 곳으로 간다(주방);
   
그곳에서 용기를 가져온다(물컵);
   
물을 있는 곳으로 가서(정수기);
   
용기에 물을 담는다;
   
물을 마신다;
    return 0;
}


<main함수와 분리한 형태>
void Drink_Water(){
   
물을 담을 수 있는 용기가 있는 곳으로 간다(주방);
   
그곳에서 용기를 가져온다(물컵);
   
물을 있는 곳으로 가서(정수기);
   
용기에 물을 담는다;
   
물을 마신다;
}
int main(){
    Drink_Water():
    return 0;
}


<함수 유무에 따른 차이>
첫 번째. 중복되는 작업이 있을 때 함수의 재사용 여부가 달라집니다. 예를 들어 물을 마시는 행위를 100번 반복한다고 가정하면, 함수가 없을 경우 5줄을 100번 적으니 500줄을 한줄씩 적어줘야 하는 반면에 함수를 사용하게 되면 처음에 5줄을 ‘Drink_Water’로 명명해 놓으면 재사용이 가능하기 때문에 5줄 대신 적는 대신 ‘Drink_Water’라는 함수를 100번 호출하면 되니 총 105줄만 적으면 됩니다.
두 번째, 추후에 어떤 변화가 생겼을 때 대응이 용이합니다. , 유지보수가 편리합니다. 예를 들어 용기가 있는 곳이 주방에서 식당으로 바뀌고, 물을 담을 수 있는 용기가 물컵에서 종이컵으로 바뀐다고 가정하면, 함수를 사용하지 않았을 경우 우리는 한번의 서술에서 2개씩, 100번의 서술이니 200번의 단어를 수정해야 합니다. 반면에 함수를 사용한 경우 함수가 정의되어 있는 부분만 수정하면 되니 2번만 단어를 수정하면 됩니다.
세 번째, 집중력의 차이가 생깁니다. 개발을 하다 보면 버그는 자주 발생하는데 함수가 없는 1000줄짜리 코드와 함수 20개로 잘 나뉘어져 있는 코드에서 버그를 찾는다 할 때, 버그 찾는 속도도 차이가 나며, 한번에 1000줄의 동작을 모두 머릿속에 넣을 수 없기 때문에 집중력에 있어서의 차이도 발생합니다.

*스택프레임에서의 시간소요

  함수를 쓰게 되면 스택프레임에 의해 추가적인 시간소요가 발생하게 되어 함수를 쓰지 않았을 때 보다 조금 느려지긴 하지만 이는 함수 이름 앞에 inline 키워드를 사용하게 되면 함수를 호출한 부분이 컴파일 시 함수의 정의부분으로 치환되므로 속도에 관한 문제가 해결됩니다.

*함수를 잘 작성하기 위한 요령

  또한 함수를 잘 작성하기 위해서는 위와 같이 생략이 많이 되어있는 문장을 풀어서 원문 그대로 생략없이 쓰는 것을 연습해야 합니다.

이제 함수의 필요성을 알았으니 위에서 간단하게 봤던 함수를 정의하는 방법과 호출하는 방법에 대해 배워보겠습니다.


2.함수의 정의와 호출
먼저 호출자와 피호출자에 대해 설명드리겠습니다. 위에서 사용했던 예제를 다시 보겠습니다.
void Drink_Water(){
   
물을 담을 수 있는 용기가 있는 곳으로 간다(주방);
   
그곳에서 용기를 가져온다(물컵);
   
물을 있는 곳으로 가서(정수기);
   
용기에 물을 담는다;
   
물을 마신다;
}
int main(){
    Drink_Water():
    return 0;
}


<호출자와 피호출자>
Drink_Water라는 함수와 main이라는 함수가 존재할 때, Drink_Water 함수를 호출하는 main호출자(caller), 호출되는 Drink_Water함수를 피호출자(callee)라고 부릅니다.


<함수 용어, 함수 정의, 함수 호출>
이제는 위의 예제를 통해 함수에 관련된 용어와 함수를 정의하는 방법 그리고 함수를 호출하는 방법과 에 대해 알아보겠습니다.
먼저 용어입니다. 함수의 구성요소로는 함수 이름’, ‘매개변수’, ‘작업 내용’, ‘반환값으로 구성됩니다.
함수 이름은 그룹지어진 명령어들의 이름이며, 하는 일을 짐작하기 쉽게 지어야 합니다. 영문자로 시작해야 하며 영문자와 _와 숫자로 구성됩니다. 숫자로 시작할 수 없으며 공백을 포함할 수 없습니다. 위의 예제에서는 ‘Drink_Water’입니다.
매개변수는 작업을 수행할 때 필요한 데이터를 변수단위로 자료형과 함께 소괄호() 안에 명시합니다. 또한 매개변수는 순서와 개수가 중요하며 함수 내에서만 사용이 가능합니다. 위의 예제에서는 매개변수가 없습니다.
작업 내용은 작업할 내용을 중괄호{} 안에 명령문으로 나열합니다. 위의 예제에서는 용기가 있는 곳으로 가고, 용기를 가져오고, 물이 있는 곳으로 가고, 물을 담고, 물을 마시는 행위입니다.
반환값은 함수의 작업 내용에 의한 결과값을 호출자에게 넘겨주는 값으로 return 뒤에 표현됩니다. 위의 예제에서는 호출자인 main에게 넘겨주는 데이터가 없으므로 반환값이 없습니다.
반환형은 반환값의 자료형입니다. 위의 예제에서는 반환형은 void입니다.
함수를 정의하는 방법은 위에서 예제의 설명처럼 함수의 기능에 맞게 함수이름, 매개변수, 작업내용, 반환형과 반환값을 작성하면 됩니다.
함수를 호출하는 방법 역시 마찬가지로 위의 예제처럼 호출하고 싶은 곳(위의 예제에서는 main)에서 호출(Drink_Water();)하시면 됩니다.
그렇다면 c언어에서의 시작은 어떤 함수부터 시작할까요? c언어로 작성된 프로그램의 시작함수는 main함수이며 main함수가 return하는 값은 OS로 넘겨지게 됩니다. 그리고 OS로 넘겨진 값은 쉘 프로그래밍에 사용됩니다.


*실행인자(Argument)
쉘에서 프로그램을 실행시킬 때, main에 넘겨지는 인자를 의미합니다. 예를 들어 test.exe라는 프로그램에서 main함수에 인자 1,2,3을 넘기고 싶은 경우 ‘test.exe 1 2 3’과 같이 입력하며 실행인자는 test.exe의 절대경로, 1, 2, 3 이 되겠습니다.


*return의 의미.
return에는 2가지 의미가 있습니다. 첫 번째, 함수를 종결한다는 의미. 두 번째, 반환값의 의미.


*void의 의미.
void의 정확한 의미는 업다의 의미가 아닌 정해져 있지 않다의 의미입니다


*main함수로 시작하는가?
컴파일러의 start entrymain으로 설정되어 있기 때문이다. start entry를 바꿔주면 다른 함수에서 시작도 가능합니다.

함수의 정의와 호출에 대해서 배웠으니 함수에 관련하여 함수의 원형에 대해 배워보겠습니다.

3.함수의 원형
위의 예제를 아래처럼 main함수와 ‘Drink_Water’함수의 서술 순서를 바꾸고 컴파일 하면 어떻게 될까요?
int main(){
    Drink_Water():
    return 0;
}
void Drink_Water(){
   
물을 담을 수 있는 용기가 있는 곳으로 간다(주방);
   
그곳에서 용기를 가져온다(물컵);
   
물을 있는 곳으로 가서(정수기);
   
용기에 물을 담는다;
   
물을 마신다;
}
main
함수에서 ‘Drink_Water’함수가 선언되어 있지 않다고 에러가 뜹니다. 이는 컴파일러는 위에서부터 아래로 코드를 번역하게 되는데 이 과정중에 ‘Drink_Water’라는 함수에 대한 정보가 없기 때문입니다. 그렇다면 어떻게 하면 될까요?
‘Drink_Water’
라는 함수에 대해서 반환형, 함수이름, 매개변수목록을 main위에 선언해주면 해결됩니다. 그리고 이렇게 선언된 형태를 함수의 원형을 선언했다.’라고 표현합니다.
그러나 이런 함수의 원형을 적는 것은 추후에 함수를 변경할 때 함수가 정의된 부분,선언된 부분 두 곳을 변경해야 하므로 되도록이면 main함수 위에 함수를 정의하는 것이 좋습니다.
, 함수의 원형을 꼭 선언해야 하는 경우가 있는데요 어떤 경우일까요? 바로 A,B라는 함수끼리 서로 호출하는 관계일 경우 어떤 함수를 위에 적더라도 컴파일러 입장에서는 다른 함수에 대한 정보가 없기 때문에 두 함수를 정의하기 전에 두 함수에 대한 선언이 필요합니다!
또한 함수 원형을 선언할 때는 매개변수에 자료형만 적어도 되지만 함수의 기능을 파악하는데 변수이름도 큰 영향을 미치므로 웬만하면 적는게 좋습니다.


 


다.표준 출력 함수

사용자가 원하는 데이터를 출력할 때 사용하는 표준 출력 함수에 대해 알아보겠습니다.


<콘솔출력 & 표준출력>
기본적으로 출력에는 표준출력과 콘솔출력이 있는데 콘솔출력이란 모니터,키보드와 같은 것들을 말하며 콘솔출력의 경우 많은 부분들이 변해 이제는 언어적 레벨이 아닌 OS레벨에서 제공하니 표준출력에 대해서만 설명드리겠습니다.
표준출력이란 각각의 시스템이 표준이라 생각하는 곳으로 출력하는 것이 표준출력 입니다. 프린터기는 종이, 음향시스템은 스피커, 컴퓨터는 모니터가 표준출력에 해당됩니다. 이런 표준출력에 사용되는 표준출력함수가 라이브러리에 내장되어있으므로 라이브러리가 무엇인지 먼저 알아보겠습니다.


<라이브러리란? && 라이브러리 등장 배경>
우선 오브젝트 파일의 특성에는 2가지가 있습니다..
첫 번째, 컴파일 속도의 향상
두 번째, 소스코드의 비공개
그러나 오브젝트 파일(.obj)을 링커를 이용해 실행파일을 만들 때 문제점이 있습니다..
목적파일안에 내가 사용하지 않은 함수도 실행파일에 포함된다는 문제점입니다.
이런 오브젝트파일의 문제점을 개선시킨 것이 라이브러리 파일(.lib)입니다.


<라이브러리 파일의 사용>
라이브러리 파일을 사용하기 위해선 라이브러리(.lib) 파일만으로는 빌드가 불가능합니다.
컴파일 시 라이브러리에 있는 함수를 호출하면, 컴파일러는 해당 함수의 원형을 알 수 없기 때문입니다.
그래서 이런 라이브러리에 들어있는 함수들의 원형을 모아 놓은 헤더파일(.h)가 필요합니다.
따라서 라이브러리를 사용하기 위해서는 라이브러리와 헤더파일이 필요하며, 소스코드 내에서 헤더파일을 include 해줘야 합니다.
그렇다면 헤더파일을 include한다는게 무슨 의미일까요?
이 내용은 전처리기에서 설명드리겠습니다.


<전처리기>
전처리기란 오로지 컴파일러에게 지시를 내리는 것입니다.
전처리기문은 명령문이 아니므로 기계어로 번역되지 않습니다.
ex)
컴파일 할 때 이 파일 추가해서 번역해주고 여기는 이렇게 컴파일 해줘~ 란 의미입니다.


1.#include “test.h”
혹은 #include <test.h>
“”는 개발자가 정의해놓은 사용자 정의 헤더파일을 include 한다는 의미.
<>
visual studio를 설치할 때 등록한 환경변수에 설치된 헤더파일을 include 한다는 의미

  *#include 에서의 “”<>의 차이
#include ””를 이용하면 사용자 정의 헤더파일을 찾아본 후 환경변수까지 찾으므로 전부 “”를 이용해서 써도 되지만 다른 개발자가 봤을 때 <>“”는 의미가 다르므로 적절히 사용할 것.
, 결국 라이브러리에 대한 헤더파일을 include 전처리기를 통해 추가로 불러와 읽으라고 컴파일러에게 지시하는 것입니다.


2.#define
컴파일시 define된 내용으로 치환합니다.
코드에서 반복적으로 쓰이는 의미 있는 상수에 대해서 define을 하게 되면 함수와 마찬가지로 추후에 유지보수시 편리합니다. 또한 의미파악도 쉽다는 장점이 있습니다.


<c언어 표준 라이브러리가 나오게 된 배경>
일단 c언어 문법이란 것은 컴파일러에 직접적으로 영향을 미침.
문법이 변경되면 그에 맞게 컴파일러도 변경되어야 함을 의미.
c
언어 이전의 언어들은 하드웨어에 종속적이며, 문법 자체에 표준출력에 대한 문법이 담겨있었음.
이는 하드웨어가 변경되면 표준출력 내용이 변경되어야 하고 이는 문법이 변경되어야 하고 이는 컴파일러가 변경되어야 함을 의미함.
이는 굉장히 비효율적임. 그러므로 c언어는 완벽하게 독립적이기 위해 하드웨어 및 시스템에 영향을 받는 요소들은 문법에서 전부 제외시킴.
대신 함수라는 기능을 제공함.
그렇다면 c언어 문법에서 표준출력함수를 제공하지 않으면 누가 제공하는가?
OS개발자들에게 떠넘김.
, 우리가 보기에 같은 printf더라도 OS에 따라 내부동작은 다른 것.
이렇게 각 OS별로 표준 함수들을 제공하고 이들을 모아 놓은 것이 표준 라이브러리다.
그럼 표준라이브러리 중 표준출력을 알아보자.


<표준 출력>
1.putchar & putc
 
단일 문자 출력 함수
2.puts
 단일 문자열 출력 함수.



읽어주셔서 감사합니다.^^