반응형
728x90
반응형

 

집에다가 Ubuntu Server 20.04를 설치했다. 

git 서버 / svn 서버 / 서비스 서버 용도로 사용하려고 하는데 GUI 환경은 필요없기 때문이다. 설치하고 놀랐던 점은 바로 아래과 같다!!!

역시 서버라 그런지 메모리 사용량이 실화인가 싶을 정도이다. (gitlab 이나 svn 서버 올리면 내나 그 타령이겠지만ㅋ)

뭐 아무튼 용도가 서버 용도인만큼 내가 원할 때 상시 접속을 할 수 있어야 하는데, 전기요금으로 인해 서버를 항시 켜둘 수도 없기 때문에 선택한 것은 WOL이다!

Wake On Lan 기능으로 랜 신호를 이용하여 컴퓨터를 깨우는 방법이다!

위와 같이 웹으로 접속하여 공유기한테 WOL 신호를 컴퓨터에게 주라고 명령하는 것이다 ㅋㅋㅋ 

그렇지만 깨움을 당하는 입장인 OS에서도 (마음의) 준비가 되어 있어야 하기 때문에 어느 정도 설정이 필요하다. 그 설정 때문에 이렇게 또 귀찮게 포스팅을 하게 되었다. 자주 쓰니까 기록할 겸!

 

1. 필요 명령 설치

sudo apt install net-tools ethtool wakeonlan

위 명령어를 통해 필요한 부분을 먼저 설치하자.

2. 이더넷 인터페이스 이름을 알아보자

내가 깨울 컴퓨터의 네트워크 랜 카드에 배정된 이더넷 인터페이스 이름을 알아야 한다. 이를 알기 위해서는 먼저,

ifconfig 명령을 실행하여

이더넷 인터페이스 이름을 알아낸다.

3. /etc/network/interfaces 설정

sudo vim /etc/network/interfaces

위의 명령을 통해 위에서 알아낸 인터페이스 이름을 기입하고 저장한다.

post-up /sbin/ethtool -s 이더넷 인터페이스 이름 wol g
post-down /sbin/ethtool -s 이더넷 인터페이스 이름 wol g

기입하고 저장한 모습

4. /etc/netplan 설정

고정 아이피만을 기준으로 적용한다. (wol 자체가 서버로 이용하려는 경우가 많기 때문에 다른 아이피들도 설정할 일이 많아서 그냥 고정 아이피로 설정해두는게 심신에 평안을 가져다 준다 ㅋㅋ)

/etc/netplan 디렉토리 내에 ~~.yaml 파일이 있는데 이를 다음 명령을 통해 열고 아래와 같이 수정한다.

sudo vim /etc/network/interfaces 

빨간 박스 부분만 추가하면 된다.

그리고 나서 아래의 명령어로 적용을 해준다.

sudo netplan apply

우분투 18.04 부터 /etc/network/interfaces 를 수정하는 것은 적용이 되지 않는다는 정보가 있어서 netplan 부분 설정이 필요해서 넣었다.

5. 시작 스크립팅 작성 ( 4번까지 적용해도 작동이 안될 경우 강제로 wol 기능을 켜도록 작성 )

먼저, 스크립트를 작성할 디렉토리를 생성한다.

sudo mkdir /etc/wol

위 디렉토리 안에 다음과 같은 내용을 작성하고 저장하여 wakeonlan.sh 스크립트를 생성한다.

sudo vim /etc/wol/wakeonlan.sh # 스크립트 생성 및 쓰는 명령어

---- 아래는 안에 들어가야할 내용 ----
#!/bin/sh                           
/sbin/ethtool -s 인터페이스이름 wol g
---- 위에는 안에 들어가야할 내용 ----

실행 퍼미션 설정

sudo chmod u+x /etc/wol/wakeonlan.sh

서비스 정의 파일(wakeonlan.service)을 작성한다. ( 컴퓨터가 켜질 때마다 실행할 서비스로 등록하는 과정 )

vim /etc/systemd/system/wakeonlan.service

---- 아래는 안에 들어가야할 내용 ----
[Unit]                                       
Description=Enable Wake-On-LAN 

[Service] Type=simple                  
ExecStart=/etc/wol/wakeonlan.sh    
Restart=always                           

[Install]                                     
WantedBy=multi-user.target         
---- 위에는 안에 들어가야할 내용 ----

아래의 명령어를 통해 서비스 등록 및 시작을 한다. 

sudo systemctl enable wakeonlan.service
sudo systemctl start wakeonlan.service

아래처럼 재부팅 후 명령어를 쳤을 때(인터페이스 이름은 각자 맡게 ㅋ), g 값이 뜬다면 성공인 것이고, 다시 컴퓨터를 종료한 후 wol 신호를 보내보자!  

728x90
반응형
728x90
반응형

C++의 새로운 형 변환

  • 기존 C언어 스타일의 형 변환 연산자

C언어 스타일의 형 변환 연산자( (자료형) 변수 및 상수 ) 형태는 C언어의 호환성을 위해서 C++에서도 제공을 한다. 그런데 문제는 C언어 스타일의 형 변환 연산자는 변환하지 못하는 대상이 없는 아주 강력한 연산자이다. 다음의 예제에서 이를 나타낸다.

39번 라인에서 Base 클래스 포인터 형을 Derived 클래스 포인터 형으로 형 변환하는 것은 일반적인 연산이 아니다. Derived 클래스 형이 필요했다면 애초에 Base 클래스 포인터 형으로 Derived 객체를 가리키게 생성할 필요가 없었다. 그리고 43번 라인은 확실하게 형 변환의 문제가 된다. 클래스 포인터 변수 pb2는 실재하지 않는 멤버를 호출한다. 왜냐하면 실제 생성된 객체는 A 클래스의 객체인데 B의 멤버 함수를 호출하기 때문이다. 이렇게 문제가 있음에도 불구하고 C 언어의 형 변환 연산자는 오류 하나 내주지 않는다.

C++ 언어에서는 이러한 문제를 보완한 4개의 연산자를 추가로 제공해준다.

static_cast / const_cast / dynamic_cast / reinterpret_cast

 

  • dynamic_cast 연산자 : 상속 관계에서의 안전한 형 변환

<> 사이에 변환하고자 하는 객체의 포인터 또는 참조형으로 구성된 자료형의 이름을 둔다.
( ) 
사이에 변환하고자 하는 대상의 이름을 둔다.

이 연산자를 통한 형 변환은 다음과 같은 형 변환 요구가 따라온다.

상속 관계에 놓여 있는 두 클래스 사이에 Derived 클래스의 포인터 및 참조형 데이터를 Base 클래스의 포인터 및 참조형 데이터로 형 변환하는 경우(Derived 클래스 포인터/참조형 -> base 클래스 포인터/참조형)

위의 요구에 부합하는 형 변환 이외의 변환이(형 변환의 방향이 반대인 경우) 해당 연산자를 통해 이루어지는 경우 컴파일러 차원의 에러가 발생한다. 아래는 그에 해당하는 예제와 실행 결과이다.

그러나 39행의 경우는 크게 문제가 되지 않고, 오히려 필요한 경우도 존재할 수 있는데 이러한 형 변환을 의도적으로 진행하는 것을 의도적으로 명시하기 위한 형 변환 연산자가 제공된다.

 

, Derived 클래스 포인터/참조형 -> base 클래스 포인터/참조형으로 변환이 가능한 경우 또한 존재한다. 다음과 같은 가정 하에.

Base 클래스가 Polymorphic 클래스(하나 이상의 가상함수를 포함하는 클래스) 일 때!

상속 관계에 놓여 있는 두 클래스 사이에서 기초 클래스에 가상함수가 하나 이상 존재하면, dynamic_cast 연산자를 이용하여 Base 클래스 포인터/참조형 -> Derived 클래스 포인터/참조형으로의 변환이 가능하다! 다음은 클래스가 수정된 해당 예이다.

47번 라인은 원래 허용되지 않았던 연산이지만 가상 함수가 존재하는 이유로 허용되는 것을 볼 수 있다. 그러나 50번 라인은 pa2가 가리키는 대상을 pb2가 가리킬 수 없는 상황이다. 변환은 가능하지만 Segmentation fault 에러가 런타임에 나오는 것을 볼 수 있는데, 해당의 경우 반환하게 되면 NULL 값을 반환한다.

이렇게 dynamic_cast 형 변환 연산자는 안정적인 형 변환을 보장해주는데 컴파일 시간이 아닌 실행 시간에 안정성을 검사하도록 컴파일러가 바이너리 코드를 생성한다.

 

  • static_cast 연산자 : A 타입에서 B 타입으로

위의 연산자는 dynamic_cast 연산자와 동일한 형태를 갖는다. static_cast 연산자는 Derived 클래스 포인터/참조형 -> base 클래스 포인터/참조형으로의 변경 뿐만 아니라, base 클래스 포인터/참조형 -> Derived 클래스 포인터/참조형으로의 변경 또한 허용한다. 그러나 그에 따른 책임은 프로그래머가 지도록 되어있다. 다음은 그 예이다.

두 변환 모두 허용은 하지만 특히, 43번과 같은 변환에 대한 결과는 예측할 수 없는 책임질 수 없는 결과가 나온다. static_cast는 프로그래머의 의도적인 변환임을, 그래서 책임 또한 지겠다는 의미가 내포되어 있다.

그리고 static_cast 연산자는 기본 자료형에 대해서도 형 변환이 가능하다.

그렇다면 static_cast 연산자는 그에 따른 결과는 연산 결과를 프로그래머가 책임지면 기존의 C 언어에서의 형 변환 연산자랑 무엇이 다를까?

기존 C 언어에서의 형 변환 연산자는 위와 같은 말도 안되는 경우를 허용한다. 그러나 static_cast로 형 변환을 진행하는 경우에는 다음과 같은 결과를 확인할 수 있다.

 

  • const_cast 연산자 : const 성향을 삭제!

위의 연산자를 이용하여 const로 선언된 참조자 혹은 포인터의 const 성격을 제거하는 형 변환을 이룰 수 있다. 이와 같은 연산은 const의 가치를 떨어뜨릴 수 있지만 다음의 예와 같이 의미를 둘 수 있다.

함수의 인자 전달 시 const 선언으로 형의 불일치가 일어날 경우 요긴하게 쓰일 수 있는 연산자이다. 그러나 const에 대한 의미가 옅어지도록 하는 요소이므로 필요할 때만 사용한다. 그런데 const_cast 연산자로 변환된 형태에 대해서 값의 변경을 시도하면 런타임 에러가 발생하기는 한다.

  • reinterpret_cast 연산자 : 상관없는 자료형으로의 형 변환

위와 같이 이전의 형 변환 연산자들과 동일한 형태를 띤다. reinterpret_cast 연산자는 전혀 상관없는 자료형으로의 형 변환을 행할 때 사용되는 연산자이다. 포인터를 대상으로 하는, 그리고 포인터와 관련이 있는 모든 유형의 형 변환을 허용한다.

위와 같은 형 변환 연산이 허용된다. 클래스 C와 클래스 D는 상속 관계가 아닌 전혀 다른 타입이기 때문이다.

위의 예제와 결과는 포인터를 대상으로 하는 포인터와 관련이 있는 모든 유형의 형 변환을 허용한다는 내용에 대한 예제인데 허용이 되지 않는 것 같다(보류)

  • 형 변환 시 발생하는 예외

위와 같은 경우는 dynamic_cast 연산자의 두 번째 경우와 같은 경우인데(NULL 반환 경우), 참조자를 대상으로는 NULL 값을 반환할 수 없기 때문에 bad_cast 예외가 발생함을 참고한다.

728x90
반응형

'컴퓨터 언어 정리 > C++ 언어' 카테고리의 다른 글

24 예외 처리  (0) 2020.10.15
23 템플릿  (0) 2020.10.13
22 string 클래스 - 직접 일부 구현  (0) 2020.10.12
21 연산자 오버로딩  (0) 2020.09.23
20 다중 상속  (0) 2020.09.22
728x90
반응형

파일의 분할

  • 첫 파일의 분할

위와 같은 예를 파일로 분할할 것이다. 다음과 같이.

< number.c / func.c / main.c >

컴파일러가 소스 코드를 컴파일 할 때는 파일 단위로 컴파일 한다. 그렇기 때문에 다른 파일의 정보를 참조하여 컴파일을 진행하지 않는다. 그렇기 때문에 저렇게 세 파일로 나누게 되면 number 변수가 어디에 있는지, set/getNumber 함수가 어디에 있는지 무엇인지 알 수 없기 때문에 에러가 난다. 그렇기 때문에 컴파일러에게 현재 컴파일하고 있는 소스의 외부 소스에 선언 및 정의가 되어있음을 컴파일러에게 알려야 한다.

다음과 같이 수정한다. 먼저 number 변수에 대해서 외부 변수라고 알린다. 그리고 두 함수에 대해서 외부 함수라는 것을 알린다. 바로 extern 키워드를 통해서 컴파일러에게 이를 알릴 수 있다.

< number.c / func.c / main.c >


< Makefile
과 실행 결과 >

 

각 소스에서 찾을 수 없는 변수나 함수들에 대해서 extern 키워드를 이용하여 외부에 존재한다는 선언을 내려주면 정상적으로 컴파일 되는 것을 확인할 수 있다. 그리고 함수에 대해서는 extern 선언은 생략해도 상관 없다. 이와 반대로 외부에서 접근을 못하게 하고 싶다면 해당 변수나 함수에 static 키워드를 붙여주면 다른 외부 파일에서 extern을 이용한 알림이 불가능하다. static 전역 변수는 접근의 범위를 파일의 내부로 제한하는 경우에 사용한다.

 

  • 헤더 파일의 활용

#include 지시자는 선행 처리기에게 다음과 같은 지시를 전달한다.

해당 #include <…> 문장의 위치에 해당 헤더의 저장된 내용을 가져다 놓기!

그리고 다음 예제를 통해서 #include 지시자를 완전히 이해할 수 있다.


< m.c / h1.h / h2.h >

 

  • 헤더 파일을 include 하는 두 가지 방법

1.     #include <헤더 파일 이름>
-> 표준 헤더 파일이 저장되어 있는 디렉터리에서 해당 헤더 파일을 찾는다.

2.     #include “ 헤더 파일 이름
-> 이 문장을 포함한 소스 파일이 저장된 디렉터리에서 해당 헤더 파일을 찾는다.
->상대 경로(...을 이용한) 및 절대 경로를 이용하여 헤더 파일을 삽입 가능하다.

 

  • 헤더 파일에 담아야 할 요소

1.     외부에 존재하는 변수나 함수에 대해서 extern 하는 선언들을 헤더 파일에 담고, 필요할 때마다 헤더 파일만 삽입하여 외부 정의를 알릴 수 있게끔 구성한다. 그렇지 않으면 모든 소스 파일에서 필요한 extern 선언을 반복해서 해야하기 때문이다.

2.     하나의 구조체 등의 선언 및 정의를 여러 소스 파일에서 사용할 때, 이를 각 소스 마다 명시하여 중복 선언 및 정의를 없앤다. 왜냐하면 중복 정의가 되어 있으면 해당 구조체를 수정하고, 확장할 때 불편함이 따른다. 그렇기 때문에 헤더 파일을 이용하여 구조체의 선언 및 정의가 하나만 존재할 수 있도록 개선한다.
 ->
헤더 파일의 중복 문제가 발생할 수 있다.

 

  • 헤더 파일의 중복 발생 및 조건부 컴파일을 활용한 중복 삽입 문제의 해결

헤더 파일의 중복 삽입 자체는 문제가 되지 않는다.

extern int number;
extern void setNumber(int n);
extern int getNumber(void);

위와 같은 선언은 여러 번 삽입된다고 하더라도 오류가 발생하지 않는다. 그저 컴파일러에게 메시지를 전달하는 것에 지나지 않기 때문이다. 그러나 변수의 선언, 구조체 정의, 구조체 변수 선언, 함수 정의 등은 컴파일러에게 메시지, 알림을 알려주는 차원이 아니라 실행 파일의 내용에 직접적인 연관이 있는, 실행 파일의 내용이 달라질 수 있는 정보이기 때문이다.

조건부 컴파일을 활용한 중복 삽입 문제 해결

위 헤더를 처음 포함하는 소스 파일은 __HEADER_H__ 매크로 상수가 정의되지 않았기 때문에 조건부 if 절을 실행하지만, 중복 포함하는 소스의 경우는 매크로 상수가 정의되어 있기 때문에 헤더를 삽입하더라도 내용이 실행되지 않는다.

728x90
반응형

'컴퓨터 언어 정리 > C 언어' 카테고리의 다른 글

19 매크로 선행 처리기  (0) 2020.10.17
18 동적 메모리 할당  (0) 2020.10.15
17 파일 입출력  (0) 2020.10.13
16 구조체  (0) 2020.10.12
15 문자와 문자열  (0) 2020.09.29
728x90
반응형

매크로 선행 처리기

  • 선행 처리기

선행 처리 과정은 프로그래머가 삽입해 놓은 선행 처리 명령문 대로 소스 코드의 일부를 수정한다. 수정은 단순 치환 형태를 띠는 것이 대부분이다. 다음은 간단한 선행 처리에 대한 예제이다.

위의 예에서 선행 처리 명령문은 2번 라인과 4번 라인, 두 가지가 존재한다. 선행 처리 명령문은 # 문자로 시작하고, 명령의 끝에는 세미콜론을 붙이지 않으며 컴파일 이전에 컴파일러가 아닌 선행 처리기에 의해 처리된다. 먼저, 4번 라인의 경우, PI라는 기호를 만나면 3.1412로 치환해라 라는 의미이다. 이 때 PI 기호를 만난다는 것은 쌍 따옴표로 둘러 쌓인 문자열 내의 PI가 아닌 모든 PI 기호를 이야기한다. 선행 처리기에 의해 선행 처리가 진행되면 오른쪽 그림과 같이 치환이 된다. 그리고 2번 라인에서 헤더 파일을 삽입하는 것 또한 선행 처리기에 의해 진행된다. stdio.h라는 파일의 내용을 include 명령이 쓰인 해당 파일에 가져다 놓으라는 의미를 가지고 있다.

 

  • Object-like macro(오브젝트와 유사한 매크로, 매크로 상수) : #define

    지시자 매크로 매크로_몸체
#define   PI   3.1412

지시자는 #include, #define 등과 같은 것을 가리켜 지시자라고 한다. 그리고 이어서 매크로라고 하는 기호와 대치되어야 할 매크로의 몸체가 등장한다. 선행 처리기는 이 부분을 보고 프로그래머가 지시하는 바를 파악한다. 그 중 #define 지시자는 다음과 같은 내용을 지시한다.

이어서 등장하는 매크로를 마지막에 등장하는 매크로 몸체로 치환!

다음은 이에 대한 예이다.

4번 라인부터 6번 라인까지 매크로 상수를 정의 및 지시가 이루어 졌고, 10번 라인부터 12번 라인에서 컴파일 이전에 치환(대치)이 이루어진다. 특이한 점은 6번 라인처럼 함수의 호출 자체 또한 매크로 몸체로 올 수 있다는 점이다.

 

  • Function-like macro(함수와 유사한 매크로, 매크로 함수) : #define

형태는 매크로 상수와 동일하다. 다음은 매크로 함수의 예이다.

지시자      매크로    매크로_몸체
#define    SQUARE(X)        X*X

기존 매크로 상수에 괄호를 등장시킴으로써 함수의 형태를 보이고 치환을 진행할 수 있다. 여기서 X는 정해 지지않은 임의의 값 또는 문장을 이야기 하는데 어떤 문자로 든 올 수 있으나 매크로와 매크로_몸체에서 사용되는 문자는 같은 문자로 통일 되어야 한다. 위의 예에서 해석을 해보자면 SQUARE(X)와 같은 패턴이 등장하게 되면 X*X 형태로 바꿔라 라는 의미다. 함수 호출과 매우 유사하고, 이렇게 변환되는 과정 자체를 매크로 확장이라고 한다. 다음은 관련 예제이다.

 

  • 매크로 함수의 문제(우선순위)

위의 예제는 매크로 함수의 잘못된 사용의 결과이다. 3+2 값의 Square 연산은 5^2으로 25가 나와야 하지만 전혀 다른 결과가 나왔다. 그 이유는 매크로 함수 치환을 풀어보면 나타난다. 3+2*3+2 형태로 치환이 되었기 때문에 우선 순위 연산에 의해서 곱셈 연산자가 먼저 연산 되는 바람에 11이라는 값이 나왔다. 이를 해결하기 위해서는 매크로 함수 정의 시 괄호를 치고 구분을 해주어야 한다.

위와 같은 정의로 매크로 함수를 구성하면 정확한 결과를 얻을 수 있다.

 

  • 매크로 기타 사항

먼저, 매크로 몸체가 복잡해서 두 줄 이상에 걸쳐서 정의를 할 경우에는 두 줄에 걸쳐서 매크로를 정의해야 하는 경우도 생기는데 이 때 그냥 임의로 줄을 변경하면 에러가 발생한다. 이럴 때에는 \문자를 이용해서 줄이 바뀐 매크로 정의임을 명시한다.

그리고 또한 이미 정의한 매크로를 이용하여 또 다른 매크로 정의가 가능하다. , 매크로 정의 시, 먼저 정의된 매크로도 포함 및 사용이 가능하다.

 

 

  • 매크로 함수의 장점과 단점

 

매크로 함수는 보통 작은 크기의 함수 / 호출의 빈도 수가 높은 함수를 매크로 함수로 보통 정의한다. 함수의 크기가 작아야 에러의 발생 확률 또한 낮고, 호출의 빈도 수가 높아야 매크로 함수가 가져다주는 성능 상의 이점을 최대한 활용할 수 있다.

 

 

  • 조건부 컴파일

#if ~ #endif : 참이라면

조건부 컴파일을 이용한 예이다. 7번의 매크로 상수를 FALSE로 정의하면 12번 라인은 실행하지 않는다.

#ifdef ~ #endif : 정의되었다면

12번 라인에서 ifdef는 해당 매크로 상수가 정의되어 있으면 ifdef 내부를 실행한다. 그리고 13번 라인처럼 비교 연산 또한 가능하다. 그리고 8번 라인처럼 공백으로 매크로 상수를 정의할 수 있다. 이 경우에는 선행 처리 과정에서 공백으로 대체되므로 소멸 의미와 같다.

 

  • #elif의 삽입 : #if만 가능

#elif를 통해서 else if 와 같은 효과를 #else를 통해서 else와 같은 효과를 볼 수 있다.

 

  •  매개 변수의 결합과 문자열화

다음과 같은 문장이 가능한지 보자.

#define CONCATENATE(A,B) “A B”

위와 같은 문장은 일단 불가능하다 왜냐하면 문자열 안에서는 매크로의 매개변수 치환이 발생하지 않기 때문이다. 이러한 경우 # 연산자를 이용한다.

#define STR(A) #A

위 문장의 의미는 A로 전달되는 인자를 문자열 “A의 내용형식으로 치환하라는 의미이다.

따옴표를 넣고 안 넣고 의 차이를 확인한다.            

이런 치환 또한 가능하다. 9번 라인에서 두 문자열을 따로 썼지만 합쳐진 결과가 나타났다. 이를 활용해보면.

#define STRCAT(A,B,C) ABC

위의 경우 STRCAT(YOO, SEUNG, HO);라고 하면 “YOO SEUNG HO”라는 문자열이 조합될 것 같지만, 실제로는 조합되지 않는다. 왜냐하면 ABC ABC의 문자열 결합으로 보지 않고 ABC를 하나의 새로운 매크로 상수로 보기 때문이다. 이 경우 ABC로 그냥 치환이 될 뿐이다.

#define STRCAT(A,B,C) A B C

이 경우에는 STRCAT(as,vf,sd); 라고 호출될 경우 as vf sd 로 치환이 될 뿐이다.

이러한 문제를 해결하기 위해서는 ##연산자를 사용해야 한다.

 

  • 필요한 형태로 단순 결합 : 매크로 ## 연산자

## 연산자를 통해 인자로 전달된 요소들을 단순 결합을 시킨다. 다음은 그에 따른 예제이다.

합쳐진 것을 볼 수 있다.

참고로 소스 코드 위에 단순 치환이기 때문에 AB라는 문자열과 1234라는 문자열을 치 ## 연산할 경우 AB1234라는 문자열이 생기는 것이 아니라 소스 코드 상에 AB1234로 대치가 되는 것이다 그렇기 때문에 AB1234는 소스 코드 상에서 변수나 함수 등의 이름으로 인지될 것이고, 이는 없는 이름이기 때문에 에러가 발생한다. 이 점을 혼동하지 않아야 한다.

728x90
반응형

'컴퓨터 언어 정리 > C 언어' 카테고리의 다른 글

20 파일의 분할  (0) 2020.10.17
18 동적 메모리 할당  (0) 2020.10.15
17 파일 입출력  (0) 2020.10.13
16 구조체  (0) 2020.10.12
15 문자와 문자열  (0) 2020.09.29
728x90
반응형

예외처리

  • 예외

프로그램 실행 도중 발생하는 예외적인 상황을 이야기 한다. 컴파일 시 발생하는 에러는 예외와는 관련이 없다. 0으로 나눗셈 등이 예외이다.

 

기존 방식의 if문을 통한 예외 처리

기존 방식의 if문을 통한 예외 처리이다. 이 경우 먼저 중요한 것은 예외가 발생하는 위치와 예외가 발견되는 위치는 다르다는 것을 알아야한다. 그리고 위와 같은 기존 방식의 if문을 통한 예외 처리의 경우 프로그램의 흐름을 구성하는 코드와 예외 처리를 위한 코드를 한 눈에 쉽게 구분하기 힘듦.

 

C++의 예외 처리 메커니즘

먼저, 위와 같이 try의 구역은 예외 발생이 예상 되는 지역을 이야기 한다. try 지역을 선정하는 것은 예외가 발생할 만한 영역 만을 선정하는 것이 아니라 이 예외와 관련된 영역 모두를 구역으로 묶고 선정해야 한다. 예외가 발생함으로써 실행 되서는 안 될 (예외 지역부터 순차적으로 실행되어 왔어야 할) 영역을 묶지 않게 된다면 문제가 되기 때문이다. , 트랜잭션의 개념으로 try 구역을 묶고 선정해야 한다.

다음은 throw 키워드를 이용하여 발생한 예외 상황에 대해서 알릴 수 있고, 알림으로써 처리를 위한 루틴(catch)으로 예외를 전달할 수 있다. 예외를 던졌다고 표현한다. throw를 통해 예외가 알려지면 해당 catch 영역 내의 해당 throw 밑의 영역은 실행하지 않고 예외가 처리되는 루틴으로 실행 흐름이 넘어간다.

마지막으로 catch 영역은 예외를 처리하는 영역이다. throw에 의해서 알려져 전달된 예외를 이 catch 블록에서 처리를 하는데 이 때, 전달된 예외의 자료형과 catch의 매개 변수의 자료형은 일치해야 전달과 처리까지 진행된다. (자료형 일치를 통해서 던져진 예외는 같은 자료형의 매개변수를 지닌 catch로 전달된다.) 이러한 catch 영역은 try에 연이어 구성되어야 하며, 하나의 try구문 당 여러 catch 구문이 구성될 수 있다. 왜냐하면 처리해야 할 예외의 자료형이 여럿일 수 있기 때문이다.

예외가 발생하지 않을 경우 try문을 실행하고, 이어서 등장하는 catch문은 뛰어 넘고 catch문 직후를 실행한다. 그러나 예외가 발생할 경우 throw에 의해 발생된 예외가 던져지고 던져진 이후의 try 문장은 실행하지 않고, 던져진 예외는 해당 예외를 감싸는 try문에 의해 감지가 되고, 이어서 등장하는 catch 블록에 전달되고 처리된다. 그렇게 처리된 후 다시 try문으로 돌아가 나머지 문장을 실행하는 것이 아니라, catch 블록 이후의 라인이 실행된다.

 

  • Throw를 통한 예외의 전달

예외가 throw에 의해 던져진 지역에서 처리가 되지 않는 경우는 함수 내에서 예외가 발생했지만 그에 따른 처리가 함수 내에서 이루어지지 않는 경우와 일맥상통하다. 위의 예제를 보면 divFunc 함수 내에서 throw에 의해 예외가 던져졌지만, 그에 상응하는 예외 처리가 divFunc 함수 지역 내에서 이루어지지 않았는데 이렇게 해당 지역에서 예외 처리가 이루어지지 않을 경우에는 예외가 발생한 함수를 호출한 영역으로 예외와 예외처리에 대한 책임이 전달된다. 위의 예에서 12번 라인에 의해 예외가 던져지면 13, 14번 라인은 진행되지 않고(함수가 종료됨) 흐름은 27번 라인으로 시작된다.

이러한 현상을 스택 풀기라고 한다. 함수 내의 또 다른 함수 호출이 이루어지는 상황에서 예외가 던져지면 함수가 호출된 순서의 반대 순서로 리턴 하듯이 종료하면서 예외가 전달된다.

 

  • 전달되는 예외의 명시

예외가 전달되는 경우가 존재하는 함수에 대해서 전달될 수 있는 예외의 종류를 명시할 수 있다. 이렇게 명시되는 예외의 종류 또한 함수의 특징이 될 수 있는데 이렇게 전달되는 예외의 종류를 명시해야만 하는 이유는 예외가 함수 내에서 처리되지 않고 전달되어 오게 되면 해당 함수 호출 부분 아래에 catch 문을 구성해야 하는데 이 catch문을 어떤 타입의 예외들로 구성을 해 놓아야 하는지를 알 수 있기 때문이다.

위와 같이 예외를 명시할 수 있다.

 

  • 예외 객체/클래스와 상속 관계에서의 catch 순서

  

위의 예제는 클래스 객체를 통해서 예외 클래스를 만들고 활용하는 예제이다. 여기서 중요한 것은 예외 객체의 상속 관계에 있는 예외들을 전달하게 되는데 받을 때 catch문의 구성이 중요하다. 먼저 왼쪽 main 함수 내의 catch 문 구성은 Base 클래스부터 catch 문을 구성하였고 아래로 내려갈수록 Derived 클래스 순으로 구성하는 것을 볼 수 있다. 오른쪽 main 함수 내의 catch 문 구성은 최하위 Derived 클래스부터 아래로 내려갈수록 Base 클래스 순으로 구성을 하였는데 이에 대한 결과는 다음과 같다.

왼쪽 main 함수에서는 전달되어 온 예외들은 모두 첫 번째 catch ExceptionA 클래스가 모두 받는다. 반면에 오른쪽 main 함수에서 전달되어 온 예외들은 정상적으로 각 예외를 적절한 catch문이 받는 것을 확인할 수 있다. 왜냐하면 Base 클래스가 Derived 클래스 형태로 전달되어 온 예외 객체를 모두 받기 때문이다.

  • new 연산자에 의해 발생되는 예외

bad_alloc 예외 클래스는 new 헤더에 만들어져 있고 메모리 공간의 할당이 실패했음을 알리는 의도로 정의된 예외 클래스이다.

  • 모든 예외를 처리하는 catch 블록

위와 같이 마지막 catch“…” 을 매개변수로 넣은 catch 문을 확인할 수 있는데 이 때, “…”의 의미는 위에서 거르지 못하고 전달되어 온 예외들을 모두 받아드리겠다는 선언이다. , 위와 같은 선언을 할 시에는 발생하는 예외에 대해서 어떤 정보도 받을 수 없고, 예외의 종류 또한 구분할 수 없다. 

  • 전달되어 온 예외, 다시 던지기!

예외를 이중으로 다시 던지는 것이 가능함을 보여주는 예이다. 하지만 간결한 구조를 위해 굳이 예외를 거듭해서 던질 필요 없다.

728x90
반응형

'컴퓨터 언어 정리 > C++ 언어' 카테고리의 다른 글

25 형 변환  (0) 2020.10.17
23 템플릿  (0) 2020.10.13
22 string 클래스 - 직접 일부 구현  (0) 2020.10.12
21 연산자 오버로딩  (0) 2020.09.23
20 다중 상속  (0) 2020.09.22
728x90
반응형

동적 메모리 할당

  • 메모리의 구성

프로그램 실행 시 운영체제에 의해서 마련되는 메모리 구조는 위와 같이 네 개의 영역으로 구분된다. 여러 영역으로 나뉘는 이유는 각기 다른 성향을 가진 여러 공간들을 유사한 성향의 공간끼리 분류해서 해당 성향을 지닌 데이터들을 분류한 공간 별로 저장하면 관리가 용이해지고 메모리의 접근속도가 향상된다.

  • 메모리의 동적 할당

함수가 매번 호출될 때마다 새롭게 할당되고 또 함수를 빠져나가도 유지가 되는 유형의 변수의 생성은 생성과 소멸의 시기가 지역 변수나 전역 변수와는 다른 유형의 변수를 생성하는 것인데 이를 가능케하는 개념이 메모리의 동적 할당이다.

malloc 함수와 free 함수

힙 영역은 프로그래머가 관리하는 메모리 공간이다. 필요한 메모리 공간을 프로그래머에 의해서 동적 할당하기 때문이다. 그렇기 때문에 메모리 할당 및 해체에 대해서 전적으로 프로그래머가 담당하기 때문에 malloc 함수로 할당한 메모리 공간은 free 함수로 꼭 해체해줘야 하며, malloc 함수에 의해 할당된 메모리 공간은 쓰레기 값으로 구성된다. 다음은 예제이다.

9번 라인을 통해 포인터 변수를 선언하고, 11번 라인을 통해 힙 영역 메모리 공간을 동적 할당하는데 이 때 인자로는 바이트 단위의 사이즈가 계산되어 전달되었고, 형 변환이 일어나고 있다. 원래 malloc 함수는 void * 형을 반환하도록 되어 있기 때문에 대입 연산을 위해서는 void * 형에서 원하는 형으로 변환을 거친 후에 참조를 진행해야 한다. malloc 함수가 void * 형을 반환하는 이유는? malloc 함수를 호출할 때 전달되는 것은 바이트 단위의 크기 정보 밖에 전달되지 않기 때문이다. 그렇기 때문에 malloc 함수는 어떤 자료형으로 참조할지 알 수 없는 상황이기 때문에 직접 형 변환 연산을 통해 변환을 진행하는 것이 그 이유이다. 그리고 18, 19번 라인을 통해 동적 할당한 메모리 영역에 값을 대입하고, 출력한다. 그리고 free 함수를 통해 동적 할당 해체를 진행한다.

  • free 함수의 호출이 필요한 이유

힙 메모리 영역은 프로그래머가 관리하는 영역이기 때문에 메모리의 동적 할당만큼 해체 또한 매우 중요하다. 할당한 만큼 해체해주지 않으면 운영체제에 의해서 메모리가 자동 해체되지만 그럼에도 free 함수를 통한 동적 할당 공간 해체가 필요한 이유는 더 큰 규모의 프로그램에서는 실행되는 동안 메모리 부족 등의 문제가 일어날 수 있기 때문에 습관적인 해체는 꼭 필요하다.

 

  • calloc 함수

malloc 함수와 똑같은 기능을 제공하나, 함수의 인자 전달 방식에 차이와 초기화 값의 차이가 있을 뿐이다. elt_size 크기의 블록 elt_count개를 힙 영역에 할당하는데, 해당 메모리 공간을 0으로 초기화 하라 라는 내용의 함수이다.

 

  • realloc 함수

realloc 함수는 ptr이 가리키는 메모리의 크기를 size 크기로 다시 조절하는 함수이다.

반환 값에 따라 두 가지 경우로 나눌 수 있다.

1.     malloc 함수가 반환한 주소와 realloc 함수가 반환한 주소가 같은 경우

-> 확장할 영역이 넉넉해서 기존 영역을 기반으로 공간을 확장한 경우

2.     malloc 함수가 반환한 주소와 realloc 함수가 반환한 주소가 다른 경우

-> 확장할 영역이 넉넉하지 못해서 새로운 영역에 별도로 할당한 경우

728x90
반응형

'컴퓨터 언어 정리 > C 언어' 카테고리의 다른 글

20 파일의 분할  (0) 2020.10.17
19 매크로 선행 처리기  (0) 2020.10.17
17 파일 입출력  (0) 2020.10.13
16 구조체  (0) 2020.10.12
15 문자와 문자열  (0) 2020.09.29
728x90
반응형

 

우분투 20.04 를 설치해보았다. UI가 너무x3 예뻐진 것이다.

원래 기존에 Desktop도 리눅스 우분투를 사용하는 나는 이를 설치를 안 할 수 없었다. 

요즘 윈도우에서 동작하는 클라이언트 프로그램에 대한 서버를 만드느라 윈도우에서 개발을 하다보니 Visual Studio를 이용한 개발이 한창이었다. 그런데 이제는 우분투에서는 Visual Studio가 없으니 CLion으로 갈아타야하는데, CLion은 내가 알기로는 CMake 설정을 직접하는 형태인 것이다. 그래서 CMake를 일단 서버에 설치를 해야한다.

그래서 이번 시간에는 CMake 설치하는 시간을 갖도록 하겠다.

CMake를 설치하기 이전에 CMake가 무엇인지 아주아주 간단하게 정리를 해보겠다.

 

1. CMake란?

CMake를 논하기 이전에 Make가 무엇인지, C언어 계열의 프로그램 작성 방법에 대해서 알아야한다.

C 계열의 프로그램 작성 방법

그리는데 너무 힘들었다 ㅋ

위의 그림과 같은 방법으로 프로그램이 작성이 된다. 예에서 나온 그림을 gcc 컴파일 명령어를 통해 나타내면 다음과 같다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
### c언어 
# 각 소스 프로그램으로 부터 object 목적 파일 생성.
gcc --o ./main.o ./main.c
gcc --o ./func.o ./func.c
# 목적 파일들을 모두 링크!
gcc -o ./program.exe ./main.o ./func.o
 
### c++언어 
# 각 소스 프로그램으로 부터 object 목적 파일 생성.
g++ --o ./main.o ./main.cpp
g++ --o ./func.o ./func.cpp
 
# 목적 파일들을 모두 링크!
g++ -o ./program.exe ./main.o ./func.o
cs

gcc 혹은 g++ 컴파일러를 이용하여 컴파일을 진행할 경우 위처럼 각 소스 프로그램마다의 목적 파일을 생성하고 이를 연결해주는 링크 작업을 진행한다.

Make

자! 그러면 Make를 설명할 때가 되었다. Make는 위의 명령들을 순차적으로, 자동적으로 실행하여 빌드하도록 돕는 파일 관리 유틸이다. 

CMake

CMake 는 멀티플랫폼으로 사용할 수 있는 Make의 대용품을 만들기 위한 오픈 소스 프로젝트라고 하는데, 사실 따지고 보면 위의 Make 파일을 만들어주는 유틸인 것이다. 즉, (빌드를 돕는) (Make를 만들어내는) 그것이 바로 CMake인 것이다.

정말 간단하게 말해서 CMake는 Make가 잘 동작하도록 도와주고 Make는 컴파일을 위한 명령들이 순차적으로 실행될 수 있도록 돕는 것이다

 

2. 설치 방법

 

2-1 . Cmake 3.18.4 버전을 따로 설치할 것이니 기존의 설치가 존재한다면 지운다!

sudo apt purge cmake

2-2 . 필요한 패키지를 설치한다.

sudo apt install wget build-essential

2-3 . Cmake 빌드를 위한 OpenSSL을 설치한다.

wget https://www.openssl.org/source/openssl-1.1.1h.tar.gz # oepnssl 다운로드
tar xzvf ./openssl-1.1.1h.tar.gz # 압축 해제
cd openssl-1.1.1h # 이동
./config # 빌드 전 config
make  
make test # 빌드 준비
sudo make install # 빌드 시작

2-4 . Cmake 3.18.4 를  다운 받아 설치한다.

cmake.org/download/

위 사이트에서 다운로드 받은 뒤 압축을 푼 후,

./bootstrap # 컴파일 환경 자동 설정

make # 빌드

sudo make install 실행 파일을 설치.

 

cmake 명령을 쳤을 때, 아래와 같이 나온다면 cmake 설치에 성공한 것이다.

위와 같은 경로에 설치되어 있는 것을 확인할 수 있다.

728x90
반응형
728x90
반응형

 

제트 브레인 사의 프로그램 ( Clion, Intellij, Pycharm 등) 을 우분투에서 다운로드 하여 압축을 풀어보면, 바로 실행할 수 있는 스크립트를 제공하기 때문에 런처에 실행 아이콘이 자동으로 제공되지 않는다.

그래서 이를 정리해보고자, 글을 작성한다.

 

1. 다운로드 및 압축 풀기

먼저, 나는 pycharm, intellij, clion 을 종종 사용하기 때문에 깔끔하게 세 개만 다운로드 후 위의 명령어로 설치를 한다.

2. 경로 재설정

접근의 편리성을 위해서 복잡한 디렉토리의 이름을 간단하게 변경한다.

그리고 해당 IDE들은 아무 디렉토리에 널부러져 있는 것 보다는 따로 공용된 곳에 관리하는 것이 낫다고 판단하여 루트 아래의 opt 디렉토리에 넣고 공통적으로 사용할 예정이다. 그래서 위의 mv 명령어로 옮겨 주었다.

3. 런처 아이콘 생성

먼저 런처에 아이콘을 생성하려면 /usr/share/applications/ 폴더 내에 .desktop 이라는 확장명의 일정 텍스트 양식이 존재해야한다. 그 안의 내용을 참조하여 실행, 아이콘 이미지 로드 등이 진행되어 런처에 추가 및 클릭으로 실행을 할 수 있는 것이다.

파일명은 무엇이든 상관없지만 아까전에 2번 항목에서 수정해줬던 이름을 그대로 따랐다.

/usr/share/applications/ij.desktop

[Desktop Entry]
Version=1.0
Type=Application
Terminal=false
Icon[en_US]=아이콘 이미지 경로
Name[en_US]=IntelliJ
Exec=실행 스크립트 경로
Name=IntelliJ
Icon=아이콘 이미지 경로

아이콘 이미지 경로나, 실행 스크립트 경로는 알아서 찾아서 넣으면 된다. jpg, sh 확장명으로 끝나는 것들을 의미하며, 제트브레인 사의 프로그램들 같은 경우는 bin 폴더 내에 모두 존재한다.

위와 같은 방식으로 나는 총 3가지를 추가하였다.

 

4. 권한 및 소유 지정

sudo chmod 644 /usr/share/applications/ij.desktop 
sudo chown root:root /usr/share/applications/ij.desktop

위에서 생성한 .desktop 파일에 권한과 소유를 지정해주어야한다!

 짠 위와 같이 잘 추가된 것을 확인할 수 있다. 이번 20.04는 아이콘들이 너무 예쁘게 잘 나왔음...

혹시라도 안될 경우는

권한 및 소유의 지정을 잘 주었는지 확인하기!
경로가 잘 지정되었는지 확인하기!

그래도 안 될 경우는 생성한 .desktop 파일을 지우고 처음부터 다시 해 본 후, 재부팅하여 확인해본다.

728x90
반응형

'리눅스:Ubuntu > Desktop' 카테고리의 다른 글

우분투 20.04 Server 에 WOL 적용하기.  (1) 2020.10.18
728x90
반응형

템플릿

  • 함수 템플릿

모형자에 비유하여 함수 템플릿을 설명

모형자는 모형을 만들어 낸다. 모형의 틀은 결정되어 있지만, 모형의 색은 결정되어 있지 않아서 결정해야함.
함수 템플릿은 함수를 만들어 낸다. 함수의 기능은 결정되어 있지만, 자료형은 결정되어 있지 않아서 결정해야함.

, 함수 템플릿은 함수를 만드는 도구가 될 수 있다. 함수의 기능은 정해져 있으나 그 기능 수행에 사용되어지는 요소들의 자료형이 결정되어 있지 않기 때문에 다양한 자료형의 함수를 만들어 낼 수 있다.

위의 예는 기본 함수를 나타낸 것이다. 정수형에 대한 곱셈 연산을 진행하는 함수이다. 이를 템플릿으로 정의하게 되면 다음과 같다.

곱셈 연산을 진행하는 함수이지만 연산의 대상에 대한 자료형은 결정되어 있지 않고, 나중에 T를 대신해서 실제 자료형을 결정하겠다는 뜻이다. 여기서 T는 자료형을 결정짓지 않겠다는 의미이고, 함수를 만들어 내는 템플릿을 정의하기 위해 사용된 것이다. 위의 예를 완성시키면 다음과 같다.

14번 라인, 15번 라인에서 <자료형> 방식을 통해 함수 템플릿의 T 를 결정 짓고 호출한다는 뜻이다. 컴파일러는 컴파일 할 때, 각 자료형에 따른 함수를 하나씩 만들고, 호출하게 된다. 컴파일 속도는 떨어지지만 실행 속도가 지연되지는 않으므로 문제가 없다.

다음과 같이 함수를 호출할 때, <자료형> 형식으로 호출하지 않고, 일반 함수 호출 방법도 허용한다. 다만, 컴파일러에 의해서 전달되는 인자로 호출될 자료형의 함수를 판단한다.

17, 18번 라인과 같이 호출 가능.

 

  • 함수 템플릿과 템플릿 함수

 

  • 두 개의 이상의 자료형을 이용하기

8번 라인에서 매개 변수의 타입은 이미 정해졌기 때문에 호출 시 18~ 20번 라인과 같이 템플릿 T1, T2의 자료형을 명시해줘야 한다. 그리고 12번 라인에서 형 변환의 두 가지 방법이 나오는데 모두 같은 효과를 지닌다.

  • 함수 템플릿의 특수화

함수 템플릿의 특수화란 함수 템플릿을 통해 템플릿 함수를 구현할 때, 구성 방법에 예외를 둘 필요가 있을 때 사용하는 방법이다. 위의 예제에서 6번 라인에서의 compare 함수는 두 인자에 대한 비교 후 큰 값을 출력하도록 되어 있는데 18번 라인과 같은 경우는 의미가 없는 비교가 된다. 왜냐하면 사전식 비교나 문자열 길이 비교가 아닌 주소 값의 비교이기 때문이다. 이러한 경우, compare 템플릿 함수의 구성에서 문자열에 대한 템플릿 함수는 의미가 없는 구성이기 때문에 다음과 같이 예외로 두고 새롭게 정의해야 한다.

12, 18번 라인과 같이 적용하면 함수 템플릿의 특수화가 적용이 된다. , 예외가 적용되는 것이다. 12, 18번 라인들의 경우 각각 char*, const char* 형의 템플릿 함수는 예외로 두고 직접 제시할 테니 해당 함수들이 호출될 경우 템플릿 함수로 구성하지 말고 가져다 쓰라는 뜻이다. 위의 예에서는 문자열에 대한 비교에서 길이 비교, 사전식 비교를 진행하는 예외 두 가지를 두었다

  • 클래스 템플릿

클래스 또한 클래스 템플릿을 정의하고 이를 기반으로 컴파일러에 의해 템플릿 클래스를 만들 수 있다.

위의 예에서 볼 수 있듯이 클래스 템플릿을 정의하고 이를 통해 템플릿 클래스의 객체를 생성할 수 있는데 25~27번 라인처럼 T에 대한 자료형을 명시한 채로 객체를 생성해야 한다. 함수처럼 생략은 불가능하다.

클래스 내의 템플릿 함수의 정의와 선언을 분리하는 방법이다.

  • 특정 템플릿 클래스의 객체를 인자로 받는 일반 함수 정의 및 friend 선언

위와 같이 일반 함수를 대상, 매개 변수로 템플릿 클래스를 받을 수 있으며 템플릿 클래스를 상대로 friend 선언 또한 가능하다.

  • 클래스 템플릿 특수화

함수 템플릿의 특수화를 하는 이유 : 특정 자료형에 대해서 구분이 되는 다른 기능을 보이기 위함.
클래스 템플릿의 특수화를 하는 이유 : 특정 자료형을 기반으로 생성된 객체에 대해서 구분이 되는 다른 행동 양식을 적용하기 위함.

19, 20번 라인과 같이 특수화시킬 수 있다.

  • 클래스 템플릿의 부분 특수화와 우선 순위

34번 라인과 같은 경우를 부분 특수화라고 한다. 부분적으로만 자료형을 지정하지 않았기 때문이다. 그렇다면 전체 특수화와 부분 특수화 모두 지정한 경우 50번 라인과 같이 겹치는 자료형에 의한 객체 생성이 발생한다면 어떻게 될까? 그 결과는 전체 특수화된 클래스를 대상으로 객체가 생성된다. , 전체 특수화가 부분 특수화보다 우선시 된다.

  • 템플릿 매개 변수와 템플릿 인자

템플릿 정의할 시에 결정되지 않은 자료형을 의미하는 T, T1 따위의 문자를 템플릿 매개 변수라고 함. 이러한 템플릿 매개 변수에 전달되는 자료형 정보를 템플릿 인자라고 한다.

이러한 템플릿 매개 변수에는 변수의 선언이 올 수 있다.

6번 라인과 같이 템플릿 매개 변수에 일반 변수의 선언이 허용되고, 템플릿 인자로 값을 받을 수 있다. 이 때 받아온 템플릿 인자를 통해서 10번 라인과 같이 멤버 변수에 대한 배열 길이를 정의 한다. 배열 길이의 정의는 생성자로도 충분히 할 수 있지만 생성자를 통해서 동적 할당을 통한 배열 구현의 경우 24번 라인에 대해서 컴파일 에러가 발생하지 않지만, 템플릿 인자를 통한 배열 구현의 경우 24번 라인에 대해서 컴파일 에러가 발생한다. 왜냐하면 <int, 5><int, 3>은 서로 다른 자료형으로 취급하기 때문이다.

  • 템플릿 매개변수의 디폴트 값

템플릿 매개변수 또한 디폴트 값을 가질 수 있다.

  • 템플릿과 static

Static 멤버는 클래스 변수로 객체 별로 생성되는 변수가 아닌 클래스 별로 생성되는 변수로서 객체가 생성되지 않은 상황에서도 이미 생성되어 있는 변수이다. 템플릿을 적용한 템플릿 클래스에서 또한 템플릿 클래스 별로 static 멤버 변수를 유지한다. , 위의 예에서 Data<int> 클래스에 하나, Data<short> 클래스에 하나씩 유지한다. 그렇기 때문에 d1d2(or d3d4)는 서로 다른 객체이지만 static 멤버가 공유된 결과를 볼 수 있다.

 

  • template<typename T …> / template <>

template<typename T …> / template <>

위 키워드의 쓰임은 함수 템플릿 및 클래스 템플릿을 정의 혹은 그 외의 템플릿 관련 정의에 대해서 위와 같은 선을 두고 템플릿의 일부 또는 전부를 정의하고 있다는 사실을 컴파일러에게 알린다. , T가 무엇인지 그리고 정의 및 특수화, 부분화 정의를 시키고 있다 라는 것을 알리기 위함이다. 전자의 경우는 템플릿 매개변수가 사용된 정의에 대해서 관련 템플릿 문자를 맞춰서 선언하고, 후자는 템플릿 문자를 사용하지 않았을 때 선언한다.

  • 템플릿 static 멤버 변수 초기화의 특수화

위 예시의 한 부분인데 위와 같이 static 멤버를 초기화하면 모든 템플릿 클래스에 대해서 0으로 초기화 된다.( Data<char>, Data<int>, Data<long>, Data<short> ) 하나의 자료형에 대해서만 다른 값으로 초기화해야 하는 경우는 다음예의 22, 23번 라인과 같이 특수화 하면 된다.

728x90
반응형

'컴퓨터 언어 정리 > C++ 언어' 카테고리의 다른 글

25 형 변환  (0) 2020.10.17
24 예외 처리  (0) 2020.10.15
22 string 클래스 - 직접 일부 구현  (0) 2020.10.12
21 연산자 오버로딩  (0) 2020.09.23
20 다중 상속  (0) 2020.09.22
728x90
반응형

파일 입출력

  • 파일 스트림

파일 입/출력을 위해서는 파일과 프로그램 사이에 입출력 스트림을 형성해야 한다. 파일은 운영체제에 의해서 그 구조가 결정되고 관리되는 대상이기 때문에, 파일 뿐만 아니라 스트림의 형성 또한 운영체제의 몫이다.

 

  • 파일 스트림의 형성 : fopen 함수와 FILE 구조체

fopen 함수는 파일과 프로그램 사이의 스트림을 형성하도록 하는 함수이다. 첫 번째 인자는 스트림을 형성할 파일의 이름(/출력의 대상 파일 경로), 두 번째 인자에는 형성할 스트림의 종류에 대한 정보를 문자열 형태로 전달(읽기/쓰기 모드 등)한다. 그리고 나서 이 함수는 첫 인자에 해당하는 파일과 스트림을 형성하고, 스트림 정보를 FILE 구조체 변수에 담아서 그 변수의 주소 값을 반환한다. FILE 구조체는 어떤 형태로 어떤 내용을 구성하고 있는지 알 필요가 없다. 단지 FILE 구조체에는 fopen함수가 호출 성공 시 해당 파일에 대한 정보가 담기고, 그러한 FILE 구조체 변수의 주소 값을 반환함으로써 FILE 구조체 포인터를 통해 가리킨다. FILE 구조체 포인터 변수는 파일(파일의 정보가 담긴 FILE 구조체 변수)을 가리키고 있는데 이러한 FILE 구조체 포인터를 이용해서 파일에 데이터를 저장하거나 파일에 저장된 데이터를 읽는다.

 

  • 입력 스트림과 출력 스트림 생성의 예

먼저, 스트림 생성 및 소멸에 필요한 함수 소개부터 진행한다.

스트림의 소멸을 요청하는 fclose 함수이다.

파일로 출력하는, 출력 스트림을 생성하는 예제

먼저, 8번 라인에서 파일 포인터를 생성하고, 10번 라인을 통해 fopen 함수를 호출함으로써 파일 출력 스트림을 생성한다. fopen 함수는 파일에 대해서 열지 못할 경우 NULL 포인터를 반환하므로 11번 라인에서 이에 대한 검사를 진행한다. (사실, 파일에 출력을 할 때에는 파일이 존재하지 않으면 파일을 생성하고 출력을 하기 때문에 딱히 검사를 하지 않아도 되긴 하다.) 그리고 16번 라인에서 fputc함수를 이용하여 ‘a’ 문자를 출력한다. 그리고 형성한 출력 스트림을 소멸 요청하는 fclose 함수를 통해 outputFilePointer에 해당하는 출력 스트림을 소멸한다. 그리고 그 결과 파일이 생성되었고, ‘a’ 문자가 잘 출력된 것을 확인할 수 있다.

 

파일로부터 입력하는, 입력 스트림을 생성하는 예제

다음은 위에서 생성한 파일 속 문자를 읽어오는 예제이다.

먼저, 8번 라인에서 파일 포인터를 생성하고, 11번 라인을 통해 fopen 함수를 호출함으로써 파일 입력 스트림을 생성한다. fopen 함수는 파일에 대해서 열지 못할 경우 NULL 포인터를 반환하므로 12번 라인에서 이에 대한 검사를 진행한다. 그리고 17번 라인에서 fgetc함수를 이용하여 문자 하나를 파일로부터 입력 받아온다. 그리고 형성한 입력 스트림을 소멸 요청하는 fclose 함수를 통해 inputFilePointer에 해당하는 입력 스트림을 소멸한다. 그리고 읽어온 ‘a’ 문자가 잘 출력된 것을 확인할 수 있다.

파일의 스트림에도 중간에 입출력 버퍼가 존재한다. 이 버퍼에 값이 저장되고 차례차례 파일로 출력을 하는 상황에서 컴퓨터의 전원이 꺼질 경우 출력되다가 처리가 되지 않은 문자들은 저장되지 않는다. 그러나 fclose 함수를 통해 파일을 닫아주면 출력 버퍼에 저장되어 있던 데이터가 파일로 이동하여 출력 버퍼가 비워지고 저장 또한 일어난다. 즉 파일을 모두 사용할 경우에는 바로 fclose 함수를 호출해줘야만 한다.

그리고 fflush 함수의 특성은 그대로 적용된다.

1.     출력 버퍼를 비움 -> 출력 버퍼에 저장된 데이터를 목적지로 전송

2.     입력 버퍼를 비움 -> 입력 버퍼에 저장된 데이터를 소멸 (버퍼의 내용을 읽으면 소멸)

3.     fflush 함수는 출력 버퍼를 비우는 함수

4.     fflush 함수는 입력 버퍼를 대상으로 호출하더라도 결과가 보장되지 않는다.

출력 버퍼를 비우는 것은 fflush 함수를 호출하면 되지만 입력 버퍼를 비우는 것은 함수가 필요하지 않는다. 왜냐하면 파일에 저장된 데이터는 콘솔 입력과는 다르게 언제든 데이터를 읽을 수 있고, 파일 대상의 입력 버퍼를 비워야만 하는 상황이라는 것은 특별히 존재하지 않기 때문이다.

 

  • 파일의 개방 모드(fopen 함수의 두 번째 인자에 대한 고찰)

스트림을 구분하는 기준

1.     읽기 위한 스트림/쓰기 위한 스트림

+는 읽기와 쓰기가 모두 가능한 스트림의 형성을 이야기하고 aappend로 덧붙임을 의미한다. 읽기와 쓰기가 모두 가능한 모드의 경우 메모리 버퍼를 비워줘야 하고 잘못된 사용으로 이어질 수 있다는 점을 주의.

2.     텍스트 데이터를 위한 스트림/바이너리 데이터를 위한 스트림

사람이 인식할 수 있는 문자를 담고 있는 파일을 텍스트 파일이라고 하고, 컴퓨터가 인식할 수 있는 데이터를 담고 있는 파일을 바이너리 파일이라 한다. 이러한 사실과 관련해서 신경 써야하는 부분은 문장의 끝을 의미하는 개행의 표현 방식이다.

‘\n’, 개행을 나타내는 문자이지만 이것은 C언어에서만 나타낸 약속이다. MS-DOS\r\n, Mac\r, Unix 환경에서는 \n이 개행이다. 그렇다면 위의 시스템들 간에 파일을 공유할 때에는 각 시스템마다 개행을 나타내는 방식으로 변환이 필요한데 파일을 텍스트 모드로 개방하게 되면 자동으로 변환이 진행된다. 반대로 바이너리 모드로 파일을 개방하게 되면 아무런 변환이 이루어지지 않는다. 텍스트 모드로 파일을 여는 경우는 사람이 읽도록 출력이 완벽하게 되어야 하기 때문에 각 시스템에 맞도록 변환이 진행되지만, 바이너리 모드로 파일을 여는 경우는 기계가 이해해야 하기 때문에 하나의 손상도 일어나선 안된다. 변환을 통한 손상 또한 일어나서는 안된다. 그렇기 때문에 바이너리 모드는 변환을 일으키지 않는다.

앞서 두 기준을 취합하여 fopen 함수의 두 번째 인자, 스트림 형성을 완성한다.

  • 텍스트 기반의 입/출력 함수

이미 언급한 함수 모두 사용 방법 및 설명이 같다. stream의 인자 부분에 표준 입/출력 디스크립터 대신 파일에 대한 파일 포인터를 넣으면 된다.

 

  • 파일이 종료되었는지 확인 : feof 함수

 

  • 바이너리 기반의 입/출력 함수

바이너리 데이터 입/출력의 경우 텍스트 데이터 입/출력의 경우보다 단순하기 때문에 다양한 함수를 제공하지 않는다.

바이너리 데이터를 입력하는 함수

fread 함수는 size 크기의 데이터 count 개를 stream으로부터 읽어 들여서 buffer에 저장하라는 의미이다.

바이너리 데이터를 출력하는 함수

fwrite 함수는 buffer에 있는 size 크기의 데이터 count stream에 저장하라는 의미이다.

 

  • 서식에 따른 데이터 입/출력: fprintf, fscanf

사용 방법은 첫 번째 인자에 stream을 넣어 파일을 대상으로 하는 것을 제외하고는 printf/scanf랑 완전히 똑같다.

  • 임의 접근을 위한 파일 위치 지시자의 이동

파일 위치 지시자란? FILE 구조체의 멤버 중에는 파일의 위치 정보를 저장하고 있는 멤버가 존재한다. 이 멤버의 값은 각종 읽기 및 쓰기 함수들이 호출될 때마다 참조 및 갱신이 된다. 이 멤버에 저장된 위치 정보의 갱신을 통해서 데이터를 읽고 쓸 위치 정보가 유지되는 것이다. 이 멤버를 파일 위치 지시자라 부른다.

그런데 파일을 읽을 때 항상 앞부터 읽는 것이 아닌, 중간부터 혹은 뒤부터 읽을 필요가 종종 생기는데 이 때 이 파일 위치 지시자 멤버를 조작하여 읽을 위치를 지정하는 것이다.

위 함수는 stream으로 전달된 파일 위치 지시자를 wherefrom에서부터 offset 바이트만큼 이동을 의미한다. offset은 음수/양수 값 모두 가능하며, 다음은 wherefrom에 전달되는 상수와 그 의미를 정리한 것이다.

다음은 위의 요소들을 종합하여 사용한 예를 나타낸다.

728x90
반응형

'컴퓨터 언어 정리 > C 언어' 카테고리의 다른 글

19 매크로 선행 처리기  (0) 2020.10.17
18 동적 메모리 할당  (0) 2020.10.15
16 구조체  (0) 2020.10.12
15 문자와 문자열  (0) 2020.09.29
14 함수 포인터와 void 포인터  (0) 2020.09.23

+ Recent posts