반응형

DHCP 깊게 알아보기

 

 

1. 원하는 IP주소 할당받기

DHCP Request Message를 자세히 보게되면 아래와 같은 [Requested IP Address]라는 항목이 존재합니다.

 

DHCP Request Option

 

DHCP Server가 할당하기를 원하는 IP주소가 아닌 다른 IP주소를 요청할 경우 어떤 응답이 올지 궁금하지 않습니까?

(크흠... 저는 궁금했습니다.)

 

 

확인해보기

위의 상황을 만들어보기 위해서 먼저 Jupyter notebook을 열었습니다.

IP주소를 일단 할당받을 이후에 새로운 IP주소를 요청하는 형태로 진행해보았습니다.

 

 

 

먼저 기존의 DHCP PACKET을 그대로 복제해서 전송해보았습니다.

 

DHCP PACKET

 

Wire Shark를 이용하여 Packet Data를 Hex Stream으로 복사한 후 해당 데이터중 dhcp의 데이터 부분만 뽑아내어 그대로 전송하였습니다.

 

여기서 특이한 부분은 UDP부분에 들어가는 CheckSum값이 틀리게 전송되고 있다는 것이었습니다.(복제되기전 패킷부터)

 

덕분에 Checksum값을 계산해주지 않고 그냥 전송할 수 있었습니다. ㅎㅎ

 

socket은 UDP이기 때문에 DGRAM으로 열어주시고, 68번 port를 Source로 67을 Destination으로 설정해주었습니다.

 

< 68번 Port로 Socket을 Bind를 해준 상태로 와이파이를 연결하면 IP주소를 받아오지 못할 수 있습니다. >

< 중간에 와이파이를 다시 잡아야할 필요가 있다면, socket close 후 진행해주세요 >

 

 

어찌되었든 패킷을 복제해서 전송해봤는데, ACK가 정상적으로 수신되는 것을 볼 수 있었습니다.

 

이제 DHCP의 메시지 중 Request IP 부분을 수정해서 다시 전송해보겠습니다.

 

IP Address 영역
Hex Data 중 IP 영역

ac(16) : 172

1e(16) : 30

01(16) : 01

23(16) : 35

 

위의 데이터의 마지막 23(16)을 64(16)으로 변경해서 전송해보겠습니다.

(64(16)은 10진수로 100입니다. 즉, 172.30.1.100 으로 요청)

 

실행결과

 

실행결과 위와같이 NAK 메시지를 수신하였습니다.

 

NAK는 'Negative ACK'를 의미합니다. 즉, 싫다는 뜻입니다. 힝...

 

NAK...

그 이후로 IP주소를 변경해가며 요청해보았지만, 전부 거절당했습니다.

 

서버측에서 DHCP Discover 단계에서 허가해준 IP주소에 한정해서 할당이 가능한듯합니다.

 

 

 

2. DHCP Discover 요청

위에서의 실패를 발판삼아 이번에는 Discover 요청부터 수행해보겠습니다.

 

마찬가지로 Discover 패킷을 복제해보았습니다. (역시나 Checksum은 맞지 않습니다.)

 

DHCP Discover

 

DHCP Discover에 대한 응답도 잘 왔습니다. (DHCP Offer)

 

다만, Discover의 경우 IP주소를 요청할 뿐 원하는 IP주소를 선택할 수 없으며, 동시에 이미 IP주소를 할당받은 상태로는 아무리해도 결과가 동일합니다.

 

고민이군요...

 

테스트하는 공간에 카페이다보니 공유기 설정창에 접속해서 만져보기가 힘드네요.

 

추후에 다른 환경에서 확인 후 이어서 포스팅 하겠습니다 ㅎㅎ

반응형
반응형

DHCP 프로토콜이란?

 

1. DHCP란?

DHCP(Dynamic Host Configuration Protocol)는 조직의 네트워크(사설망)의 호스트에게 동적으로 IP주소를 할당하는 프로토콜입니다.

예를들어 본다면, 쉽게 생각하여 IP를 할당하는 공유기에 PC를 연결하면 자동으로 192.168.~ 로 시작하는 IP주소를 자동으로 할당해주는 것과 같습니다.

 

개인 또는 소규모 사업체의 경우 위와같은 공유기 등을 이용하여 사설IP를 할당받아 사용하거나, 각 호스트별로 IP를 관리자가 직접 할당하지만, 호스트가 네트워크 케이블을 연결함과 동시에 자동으로 IP를 할당받아 통신이 될 필요가 있으면서 연결된 호스트 IP를 정해진 규칙에 맞에 관리할 필요가 있다면 DHCP를 선택하는 방법이 좋습니다.

 

 

 

2. DHCP의 장단점

 

DHCP의 장점

 : DHCP 이전에는 네트워크 관리자가 IP주소를 관리하기 위해서는 [ MAC주소 - IP주소 ]를 관리자가 직접 맵핑하여 서버에 저장해 두고 PC와 같은 노드(node)가 부팅되면 BOOTP라는 프로토콜을 이용하여 서버로부터 IP를 알아오는 방식이었습니다.

이러한 방식은 매번 새로운 IP장비를 연결할때마다 관리자가 직접 IP주소와 MAC 주소를 맵핑시켜줘야 할 필요가 있습니다. 하지만 DHCP방식이 도입되면서는 자동으로 PC에 IP주소를 할당해주기 때문에 이러한 문제를 해결할 수 있었습니다.

 

또한 DHCP는 서버에서 모든 사설 IP주소를 관리하기 때문에 IP주소 충돌과 같은 문제를 예방할 수 있습니다.

 

 

DHCP의 단점

: DHCP는 서버가 자동으로 IP를 할당하기 때문에 몇가지 문제가 있습니다. 예를 들어 A라는 PC가 192.168.0.2라는 IP주소를 갖고 있다가 연결이 해제되면 해당 IP주소를 다른 B라는 PC가 할당받을 수 있습니다. 일반적인 경우라면 문제가 없겠지만, 만약 포트포워딩으로 특별한 서비스를 제공하는 경우 정전 등에 의해 공유기(DHCP 서버)가 재부팅될 때, 갑작스럽게 IP주소가 변경되어 서비스가 불통이 될 수 있습니다.

 

또한 IP주소를 기반으로한 인증방식을 갖고 있는 서비스가 사설 네트워크에 제공된다면 이것 역시도 문제를 발생시킬 수 있습니다.

 

이러한 문제들 외에도 보안과 서비스에 관련된 문제들이 있고 이러한 문제를 해결하는 여러 방식들이 존재합니다.

 

 

 

3. DHCP의 원리

DHCP의 IP주소 할당방식은 'IP주소 임대'가 핵심입니다.

특정 장비에 특정 IP주소를 일정 기간동안 빌려주는 방식이고, 해당 시간이 끝나면 다시 서버가 회수합니다.

이 과정에서 장비와 서버는 [IP주소 임대 요청], [임대기간 연장], [IP주소 사전 반납], [재연결 요청] 등의 통신을 수행합니다.

 

 

4. DHCP PACKET

DHCP는 Client는 UDP 68번, Server는 UDP 67번을 사용합니다.

 

 

1) DHCP DISCOVER ( Client -> Server )

 

 

DHCP Discover
DHCP Discover

DHCP Discover Message는 위와같이 네트워크 전체에 '브로드캐스팅'되는 것을 볼 수 있습니다.

 

Client가 Server에 전송하는 Packet으로 Client는 자신의 MAC 주소를 네트워크에 브로드캐스팅합니다.

 

브로드캐스팅된 해당 패킷을 DHCP 서버가 확인하면 'DHCP 서버의 IP주소'와 'DHCP 서버가 할당할 수 있는 IP주소'를 해당 장비에게 전달합니다. (DHCP OFFER)

 

 

2) DHCP OFFER ( Server-> Client )

 

DHCP Offer
DHCP Offer

 

위에서 보면 [172.30.1.254 = 공유기(DHCP 서버)]가 [172.30.1.35 = Client에게 할당하려는 IP주소]에게 DHCP Offer Message를 전송하는 것을 볼 수 있습니다.

 

특이한 것은 DHCP Server가 목적지로 하는 172.30.1.35는 아직 Client의 IP주소가 아니라는 것입니다. 때문에 Client 측에서는 해당 패킷을 수신하면 이더넷(Layer 2) 계층에서는 자신의 MAC 주소를 목적지로 하기 때문에 상위계층(Layer 3, Network)으로 올려 보내지만 해당 Layer에서는 자신을 목적지로 한 IP주소가 아니라고 생각합니다. 해당 장비가 Routing 기능이 활성화되어있다면 어떻게 될지 궁금하네요.

 

어찌 되었든 해당 DHCP Offer Message를 좀 더 보겠습니다.

 

DHCP의 메시지 영역을 보면 아래와 같은 내용이 있습니다.

 

DHCP Offer

해당 내용은 현재 IP주소를 할당하려는 DHCP 서버가 Client에게 할당하려는 IP주소와 네트워크의 정보들을 공지한 것입니다.

 

해당 내용을 참고하여 Client는 해당 IP주소를 사용할 것인지를 판단하고, 해당 IP주소를 할당받기를 원한다면 DHCP 서버에게 DHCP REQUEST Message를 전달합니다.

 

 

3) DHCP REQUEST ( Client -> Server )

 

DHCP Request
DHCP Request

 

DHCP Request는 DHCP Server가 알려준 IP주소와 네트워크 정보를 잘 수신하였고, 해당 IP주소를 할당받겠다는 응답을 전달하는 역할을 수행합니다.

 

위에 보시면 아직 송신지 주소는 0.0.0.0인 상태이며, DHCP Server의 IP주소로 내용을 전달하는 것이 아니라 브로드캐스팅을 수행하는 것을 볼 수 있습니다. 즉, 아직 Client는 IP주소를 할당받지 않은 상태임을 말합니다.

 

서버는 Client의 DHCP Request Message를 수신하면, 해당 내용을 확인했으며 IP주소를 사용해도 좋다는 응답(DHCP ACK)을 보냅니다.

 

 

 

4) DHCP ACK ( Server -> Client )

 

DHCP Ack
DHCP Ack

 

DHCP Ack Message를 수신한 Client는 이제부터 해당 IP주소를 사용하여 네트워크에 패킷을 전송할 수 있습니다.

 

즉, 이 시점부터 IP임대가 시작되는 것입니다. (172.30.1.35 가 저의 IP주소가 되었습니다.)

 

DHCP ACK 패킷을 좀 더 보겠습니다.

 

DHCP Option

DHCP의 Option을 보면 위와 같이 IP주소의 임대 기간이 나와있습니다.

 

해당 DHCP Server는 정확하게 1시간을 임대기간으로 할당하는 것을 볼 수 있습니다.

 

만약 1시간 동안 PC에서 IP 주소 임대 연장에 관련한 요청이 없다면 해당 IP주소는 1시간 후 회수됩니다.

 

그럼 이제 임대연장에 대해 보겠습니다.

 

 

 

5) DHCP IP 임대기간 연장

 

DHCP에서 IP주소의 임대기간을 연장할 때에는 앞서 사용한 DHCP Request Message를 사용합니다.

 

DHCP Request & Ack

위에 패킷을 보게 되면 [172.30.1.35]라는 제가 위에서 할당받았던 IP주소를 사용하여 DHCP 서버(172.30.1.254)에 DHCP Request 메시지를 보내는 것을 볼 수 있습니다.

 

앞서 0.0.0.0으로 브로드캐스팅했던 것과 큰 차이가 있습니다.

 

패킷의 Option 부분에도 큰 차이가 있습니다.

 

DHCP Request ( Client -> Server )

클라이언트가 보낸 메시지에는 위와 같은 내용이 있습니다.

 

즉, IP주소의 임대기간을 90일로 변경해달라는 내용입니다. 기존에 1시간을 할당받았던 것을 생각해보면 너무 파격적이네요.

 

그리고 응답(DHCP Ack)을 보겠습니다.

 

DHCP Ack ( Server -> Client )

응답을 보게 되면 Client의 90일은 할당해주지 않았지만, 위와 같이 1시간으로 임대기간을 변경해 준 것을 볼 수 있습니다.

 

기존에 1시간을 받았다가 30분 정도가 지나 30분이 남았던 임대기간을 1시간으로 재조정한 것입니다.

 

 

 

5. 다음 이야기

이번에 알아본 DHCP의 네 가지 [Discover, Offer, Request, Ack]이외에도 몇 가지 Message들이 존재합니다.

 

물론 위의 네 가지가 가장 압도적인 비율을 차지합니다만, 다음 페이지에서는 위 네가지 이외의 메시지들과 Client 측에서 DHCP Server를 귀찮게 하는 몇 가지 것들을 알아보겠습니다.

반응형
반응형

1. 가상환경 생성

conda create -n [가상환경이름] python=[버전]

 

ex) conda create -n datasci python=3.7

 

 

2. 가상환경 목록 확인

conda info --envs

 

3. 가상환경 실행

conda activate [가상환경이름]

 

ex) conda activate datasci

 

 

4. 가상환경 해제

conda deactivate

 

 

5. 가상환경 제거

conda remove -n [가상환경이름] --all

 

ex) conda remove -n datasci --all

반응형
반응형

 

1. 회선교환 (Circuit Switching) 방식

회선교환 방식은 회선 독점을 통한 통신방식이라고 볼 수 있습니다.

 

아래와 같은 간단한 네트워크망이 존재한다고 가정하겠습니다.(실제로는 대부분 전화망에서 사용하기 때문에 구성이 조금 다릅니다.)

 

(PC와 교환기로 이루어진 네트워크망입니다.)

(교환기는 단순히 전용선을 할당하는 장비라고 생각하셔도 무관합니다.)

 

 

네트워크망 예시

회선교환 방식의 가장 큰 특징은 [전용선 할당]에 있습니다.

 

예를들어 전송할 데이터가 있다고 하면 아래와 같이 전송을 위한 전용선을 할당하고 해당 선로로 모든 데이터를 전송합니다.

 

회선교환 방식

위 그림과 같이 송수신을 연결하는 전용선을 설정하고 전송을 하는게 핵심입니다.

 

개념을 이해하셨다면 특징에 대해 말해보겠습니다.

 

 

 

2. 회선교환의 특징

  • 회선교환은 통신 회선을 설정하여 데이터를 교환하는 방식
  • 회선 교환방식으로 음성 전화 시스템에 사용됨
  • 송신자의 모든 데이터는 동일한 경로로 전송됨
  • 안정적인 통신이 가능함
  • Point-To-Point 방식으로 연결됨
  • 통신중 중간경로에 문제가 발생할 경우 전체 연결이 끊어짐 (새로운 경로를 통한 새로운 회선할당 필요)

 

장점

 - 대용량 + 고속 데이터 처리에 우수

 - 고정적인 대역폭을 사용

 - 연속적인 데이터 처리에 우수

단점

 - 회선 이용 효율이 떯어짐 (대역폭 낭비)

 - 통신과정에서 회선문제시 회선할당부터 다시해야함

 - 통신비용이 고가임

 

 

 

3. 패킷교환 (Packet Switching) 방식

회선교환 방식과 비교된다면, 대충 눈치채셨을 수 있습니다. 패킷교환은 회선교환과 다르게 [전용선]의 개념이 없습니다.

 

패킷교환은 전송하려는 데이터를 패킷이라는 단위로 나눠 네트워크망으로 뿌려주게됩니다.

 

이때 패킷에는 해당 데이터가 어떤 데이터의 몇번째 데이터인지의 정보와 최종 목적지에 대한 정보가 들어있습니다.

 

위의 정보를 라우터가 보고 패킷을 최적경로를 향해 전달하게 됩니다. 이때 최적경로는 단순하게 거리만을 계산하는 것이아니라, 망의 혼잡도(대역폭 사용율), 연결상태, 기타 설정등에 따라 그때그때 변경될 수 있기 때문에 경로는 수시로 변경될 수 있습니다.

 

따라서 특정한 데이터가 100개의 패킷으로 분해되어 전송된다면, 100개의 패킷들은 라우터에의해 서로다른 경로로 전송될 수 있고, 최종적으로 목적지에 100개의 패킷이 전달되면 패킷의 순서를 통해 다시 원래의 데이터로 합쳐지는 방식입니다.

 

 

(PC와 라우터로 이루어진 네트워크 망입니다.)

(라우터는 데이터의 최적경로를 파악하고 결정하여 송신하는 장비로 일단은 공유기의 상위호환 정도로 생각하셔도 괜찮습니다.)

 

패킷교환 방식

위의 그림과 같이 3개의 패킷이 [왼쪽]에서 [오른쪽]으로 전송될 때, 각각의 패킷은 서로 다른 경로로 전송될 수 있습니다.

 

또한 이러한 특성 때문에 전송되는 패킷은 순서와 다르게 수신될 수 있습니다.

 

 

 

4. 패킷교환의 특징

  • 전송되는 패킷은 여러 경로를 이용가능(패킷별로 최적의 경로 선택)
  • 송신 패킷의 순서와, 수신 패킷의 순서가 다를 수 있음
  • 전송 속도 및 흐름 제어가 가능
  • 에러 탐지가 가능(패킷정보를 통해)
  • 일반적인 인터넷 망에서 사용됨

 

장점

 - 회선의 이용률이 높음

 - 애러 및 장애에 강함 : 라우터 고장시 다른 경로를 즉각적으로 이용, 애러에 대해 특정 패킷만 재전송 가능

 - 인터넷 뿐만 아닌 다양한 통신망에서 사용가능(전화도 가능)

단점

 - 경로 탐색과정에서 지연이 발생됨

 - 전송량 증가에 따라 지연율이 급격하게 상승

 - 패킷헤더 추가로 인한 오버헤드 발생이 가능함

 

 

 

 

5. 가상회선(Virtual Circuit) 기술

가상회선 기술은 위의 패킷교환방식의 네트워크에서 회선교환과 같은 통신을 만들어주는 기술입니다.

 

패킷단위로 데이터를 전송하지만, 사전에 논리적으로 구성된 특정한 경로로 데이터를 전송하게 됨으로 모든 패킷은 동일한 경로를 통해 데이터가 전송됩니다.

 

수신측은 위의 특징 때문에 순차적으로 패킷을 수신할 수 있습니다.

 

다만 전송을 수행하기 전에 앞서 언급한것처럼, 경로를 결정해야하고 이를 위해 몇가지 특수한 패킷들을 네트워크에 전송할 필요가 있어 처음 연결에 약간의 시간이 필요하며, 또한 경로만 일정할 뿐, 전용회선이 아니기 때문에 설정한 경로를 통해 다른 노드에 의해 대량의 데이터가 유입될 경우 네트워크 지연이 발생할 수 있습니다.

반응형
반응형

0. 네트워크는?

네트워크(Network)의 어원은 Net+work로 '그물을 짜는 일'에서 나왔다고 합니다.

네트워크는 의미있는 정보인 데이터를 전송하는 복잡한 그물이라고 볼 수도 있겠네요.

 

하지만, 만약 누군가에게 네트워크의 정의에 대해 말해줄 일이 생긴다면 아래처럼 말할꺼 같습니다.

 

네트워크란 두 개 이상의 노드가 서로의 자원을 고유할 수 있는 데이터 통신 환경.

 

뭔가 유식해보이는 느낌을 주니까요.ㅎㅎ

 

 

본론으로 들어와서 네트워크는 크게 유선과 무선으로 나눠볼 수 있다고 생각합니다. 무선네트워크라하면 LTE, WiFi 같은 녀석들이 있을 것이고, 유선이라고 한다면 PC와 셋톱박스의 연결되는 인터넷이 대표적이겠죠.

 

앞으로 이러한 네트워크 환경을 구성하는 다양한 프로토콜과 이론들에 대하여 알아보도록 하겠습니다. 먼저 간단한 이론부터 시작하겠습니다.

 

1. 거리 기반 네트워크

종류 범위 특성
PAN (Personal Area Network) 약 5m 전후의 인접 통신 초 인접지역 통신으로 일반적으로 무선의 WPAN이 주
LAN (Local Area Network) 근거리 네트워크로 사무실과 같은 소규모 공간 내의 고속 통신회선 WAN보다 빠른 통신 속도, 단일 기관 소유의 네트워크, Peer-to-Peer 형태가 주
MAN (Metropolitan Area Network) LAN과 WAN의 중간형태 광케이블, 동축케이블을 통한 전송, DQDB 프로토콜
WAN (Wide Area Network) 광대역 네트워크망으로 유관한 LAN간의 연결 목적지를 결정하는 경로 탐색 알고리즘이 중요

 

* DQDB (Distributed Queue Dual Bus) : IEEE 802.6 표준 MAN 프로토콜, 이중 버스형태에 분산 큐를 통한 큐잉방식을 통한 전송방식.

 

 

 

2. 데이터 전송 방식

유무선 통신방식은 크게 3가지 형태로 나눌 수 있습니다.

 

단방향 통신 (Simplex)

일방적으로 'A->B' 의 통신방 가능한 전송방식

Simplex
반이중 통신 (Half Duplex)

서로 데이터를 전송할 수 있지만, 하나의 회선을 사용하기 때문에 동시에 전송은 불가능 (ex, 무전기)

Half Duplex
전이중 통신 (Full Duplex)

서로 언제나 필요한 데이터를 동시에 송수신 할 수 있는 전송 (ex, 전화)

Full Duplex 

 

 

3. 네트워크 토폴로지 (Network Topology)

네트워크 토폴로지는 물리적으로 연결된 형태에 따른 분류라고 볼 수 있으며, 다르게 말해 통신망의 구조에 따른 분류라고도 볼 수 있습니다.

 

크게 '링크'와 '노드'라는 요소로 이루어져있습니다.

 

링크(=회선) : 두 노드를 연결하는 선으로 PC에 연결되는 인터넷선(랜선)을 생각하면 이해하기 좋습니다.

노드 : 뒤에서 다루겠지만, 네트워크의 데이터를 송수신하고, 이 데이터를 처리하는 장치를 말합니다. 대표적으로 PC(단말노드)와 라우터가 있습니다.

 

 

네트워크 토폴로지에 의한 분류는 크게 5가지로 나눌 수 있습니다.

 

계층형 (Tree)

장점

 *. 네트워크 관리가 쉽고, 새로운 장치를 추가하기 쉬움

 * 네트워크의 신뢰도가 높음

 

단점

 * 트래픽 집중에 따른 속도 저하현상(병목현상)이 발생하기 쉬움

 * 상위 노드 고장시 상위 네트워크와의 통신이 불가능

버스형 (Bus)

장점

 * 설치비용이 적고, 신뢰성 우수

 * 구조 간단

 * 새로운 노드 추가가 쉬움

 

단점

 * 네트워크 병목현상 발생이 쉬움

 * 장애 발생시 전체 네트워크 마비

 

특징

 * 회선의 양 종단에는 터미네이터(Terminator)가 존재하여 신호의 반사를 차단합니다.

성형 (Star)

장점

 * 고속 네트워크에 적합

 * 노드 추가가 쉬움

 * 개별 링크 장애시에도 네트워크에 영향이 없음

 

단점

 * 중앙 노드 장애시 전체 네트워크 불통

 * 노드 증가에 따라 네트워크의 복잡도가 증가함

링형 (Ring)

장점

 * 저렴한 네트워크 구성이 가능

 * 충돌현상이 발생하지 않음

 

단점

 * 네트워크의 구성을 변경하기 힘듬

 * 링크 장애시 전체 네트워크 불통

 

특징

 * Token Passing기법을 사용한다.

망형 (Mesh)

장점

 * 완벽하게 이중화 되어있으므로 장애에 강함

 * 많은양의 데이터 처리에도 문제없음

 

단점

 * 구축과 운영 비용이 매우 고가 

반응형
반응형

0. 시작하기

 

앞으로 진행될 거의 모든 내용에서 클래스를 다룰것 같습니다. 또한, 클래스에 사용되는 것들에 대해서만 언급해도 끝이 없습니다. 때문에 다음 내용부터는 대부분 [클래스의 ㅇㅇㅇ]이라는 느낌으로 진행될듯합니다.

 

 

1. 객체지향 프로그래밍 ( OOP : Object Oriented Programing )

사실 객체지향이라는 것에 대해서만 다뤄도 짧지 않습니다만, 간단하게만 집고 넘어가겠습니다.

 

객체지향 프로그래밍이 어떤 것이다라고 정의해둔 글들이 많이 있습니다만, 제 생각에 가장 간단하게 말하자면 아래와 같습니다.

 

객체지향 프로그래밍(OOP)은 프로그램이 사용하는 유관한 데이터들을 조합하여 묶어줌으로 객체(사물, 사람, 대상)를 만들어주고, 여기에 객체가 행할 수 있는 행동을 정의해줌으로써 각 객체가 상호작용을 수행할 수 있는 프로그래밍을 의미한다.


라고 생각합니다. 사람마다 생각하는것에 차이가 있기 때문에 제가 생각하는 객체에 대한 정의가 다른사람이 볼때는 잘못됬다고 생각할 수 있습니다.

어찌되었든 글로 표현하면 이해하기가 힘든 내용입니다. 때문에 간단하게 그림을 통해 알아보겠습니다.

 

먼저 객체의 정의가 필요합니다.

객체의 정의란, 어떤 객체가 취할수 있는 상태(state, 멤버변수)를 정의하고, 객체가 행할 수 있는 행동(Action, 멤버함수)을 정의하는 것입니다.

 

간단하게 사람이라는 객체를 정의한다고하면 아래와 같은 것들을 의미합니다.

 

여기서 중요한 것은 가변하는 값을 갖은 멤버 변수는 이에 대응하는 멤버 함수가 존재해야합니다.

 

예를들어 '몸무게'라는 멤버변수는 '식사하기'라는 멤버함수에 의해서 제어된다는 식의 개념이 필요합니다.

 

물론, 고정되는 값을 갖는 멤버변수도 존재합니다. 만약 위의 데이터가 한달만 사용되는 데이터라면, '나이', '성별'등은 특별한 경우가 아니라면 고정된 값을 갖은 멤버변수일 것입니다.

 

만약 위와같이 멤버변수, 멤버함수를 통해서 객체(=사람)을 정의했다면, 다음은 정의한 객체들을 구분할 이름을 할당합니다.

 

 

 

위와같이 각각의 이름을 할당받은 객체들은 앞서 정의한 기준에 따라 초기화를 해주게됩니다.

 

초기화는 각각의 객체가 갖고있는 상태를 저장하는 것을 말합니다. 예를들어 철수의 현재 나이는 7살이고, 키는 70cm, 몸무게는 20kg등의 정보등을 입력하는 과정을 말합니다.

 

객체의 초기화가 끝나면 각 객체들은 정의해준 멤버함수를 통해서 서로 상호작용이 가능합니다. 물론 단순히 자신의 값만을 변경하는 것도 가능합니다.

 

위와같은 과정을 이제 코드를 통해서 다뤄보겠습니다.

 

2. Class ( CPP/Class.cpp )

객체지향 프로그래밍(OOP : Object Oriented Programing)의 꽃 Class에 대해서 다뤄보겠습니다.

#include <iostream>
using namespace std;

class User{
private:
    int age;
    int tall;
    int weight;
    bool sex; // true: 남성, false: 여성

public:
    void Init(int age, int tall, int weight, bool sex){
        this->age = age;
        this->tall = tall;
        this->weight = weight;
        this->sex = sex;
    }
    void prtInfo(){
        cout << "age : " << age << endl;
        cout << "tall : " << tall << endl;
        cout << "weight : " << weight << endl;
        cout << "sex : " << sex << endl << endl;
    }
};

int main(void){
    User Hun2, Chulsu;
    Hun2.Init(7, 30, 20, true);
    Chulsu.Init(7, 32, 22, true);
    Hun2.prtInfo();
    Chulsu.prtInfo();

    return 0;
}

실행결과

 

정말 간단한 Class 예제입니다.

 

User라는 클래스를 보게되면 먼저 [ private 와 public ]이라는 두가지를 볼 수 있습니다. 바로 '접근제한자'라는 것입니다. 다음에 자세하게 다뤄보겠지만, 간단히 말하면 private는 해당 class내의 함수에서만 해당 변수들에 접근이 가능하다는 것을 말하며, public은 어디서든 해당 함수나 변수에 접근이 가능하다는 것을 말합니다.

 

private 로 선언된 부분에 멤버변수를 적어주었고, public으로 선언한 부분에 멤버함수를 적어주었습니다. private로 멤버변수에 접근하지 못하게 하는것을 '정보은닉'이라고 합니다. 굳이 사용자가 알아야할 정보가 아닌것에 접근하는것을 차단하기 위한 방법이죠.(접근제한자 설명시 다시 언급하겠습니다.)

 

다음으로 this라고 되어있는 부분을 볼 수 있습니다. 'this'는 현재 접극하는 객체의 주소값을 갖고있는 녀석을 말합니다.

 

만약 '훈이'라는 객체가 만들어지고 해당 객체에서 'this'가 포함된 함수를 실행하면, 거기서 'this'는 훈이라는 객체의 주소가됩니다. 따라서 [this->변수]를 통해서 해당 객체가 갖고있는 멤버변수에 접근이 가능합니다.

 

따라서 위의 Init부분을 보게되면 같은 age라는 변수명을 사용했음에도 서로 구분되어 사용이 가능하게됩니다.

 

 

반응형
반응형

0. 시작하기

 

 

1. New 키워드에 대해서

앞에서 동적할당에 대해서 간단하게 말씀드렸습니다. 여기서는 메모리와 연결해서 조금 더 자세하게 알아보도록 하겠습니다.

 

int *pt = new int[10];

위의 간단한 코드를 메모리측에서 보면 Heap영역과 Stack영역을 나눠서 봐야합니다.

 

앞서 다른 게시물에서 언급했듯 Heap영역은 동적할당시 사용되는 메모리 영역이며, Stack영역은 지역변수(변수,포인터, 참조자 등 모두해당됩니다.)가 사용하는 메모리 영역입니다.

 

따라서 위의 과정을 메모리 영역에서 보면 아래와 같은 모습을 볼 수 있습니다.

 

 

int *pt = new int[10] 에 대한 메모리 추상화

 

new int[10] 이라는 코드가 heap영역에 공간을 확보하고 해당 공간의 주소값을 반환한다고 했는데, 확인해보겠습니다.

 

#include <iostream>
using namespace std;

int main(void){
    cout << (new int[10]) << endl;

    return 0;
}

실행결과

 

 

위와같이 new 키워드의 결과를 출력하면 주소값을 반환하는 것을 볼 수 있습니다. 때문에 주소값을 저장하기 위해 포인터(*)를 사용하는 것입니다.

 

위에서 heap영역과 stack영역을 구분해서 말씀드린 이유가 있습니다!!

 

가장 중요한 이유, 그것은 stack영역에 저장되는 데이터는 함수가 종료되는 시점까지만 유효한 값을 갖습니다.(지역변수) 반면 heap영역은 할당받은 메모리를 반환할때까지 유효한 값을 유지합니다.

 

이에대한 두가지 예제를 보겠습니다.

 

첫번째는 stack 영역에 저장된 값의 주소를 반환한 경우입니다.

 

#include <iostream>
using namespace std;

int* func(){
    int x=10;
    return &x;
}


int main(void){
    int *pt = func();
    cout << *pt << " : " << pt << endl;

    return 0;
}

경고 메시지
실행결과

 

해당 코드를 실행하면 위와같은 [경고메시지]와 [실행결과]를 볼 수 있습니다.

 

다음으로는 heap 영역에 저장된 값의 주소를 반환하는 경우입니다.

 

#include <iostream>
using namespace std;

int* func(){
    int *x = new int;
    *x = 10;
    return x;
}


int main(void){
    int *pt = func();
    cout << *pt << " : " << pt << endl;

    return 0;
}

실행결과

 

후자에서는 [경고메시지]없이 [실행결과]를 얻을 수 있었습니다.

 

의문이 들 수 있습니다. [경고메시지가 출력되기는 했지만, 실행만 잘 되면 괜찮은거 아닌가요?] 라는 의문.

 

답은 절대로 안됩니다!

 

 

 

여러분이 프로그램을 설계할 때, 심심하면 한번씩 이상한 값을 출력하길 원하는게 아니라면, 절대 위와같은 경고메시지(⚠️)를 무시하시면 안됩니다.

 

위의 경고메시지가 발생하는 원인은 한가지 입니다.

 

-> 유요하지 않은 값, 즉 해당 메모리는 다른 값에 의해서 언제든지 덮에 씌워질 수 있는 상태라는 것입니다.

 

새로운 값이 스택영역에 저장되면 해당 공간에는 다른 값이 저장되고, 그때는 10이 아닌 다른 값을 출력할 것입니다. [경고메시지]의 출력을 제거한 상태라면 위와같은 문제로 프로그램이 꼬였을 때, 문제를 찾기도 힘들죠.

 

반면 후자에서 사용한 heap영역의 주소를 반환했을 때에는 함수가 종료된 후에도 [heap 영역의 데이터] 가 유효한 상태를 유지하기 때문입니다. heap영역의 데이터는 프로그램이 종료되거나, 해당 공간을 os에 반환할 때까지 유효한 값을 갖습니다.

 

 

 

OS로부터 할당받은 공간을 사용 후 반환, 그리고 다시 해당 값을 참조하는 것은?

 

이건 더 큰 문제를 야기할 수 있습니다. heap영역은 가변가능한 공간입니다. 사용하지 않을경우 OS에 반환(또는 회수)되는 공간이죠.

 

이렇게 반환된 공간은 다른 프로그램에 의해서 사용될 수 있습니다.

 

운이 나쁘다면(?) 반환된 공간을 참조함에따라 다른 프로그램이 사용중인 영역의 값을 프로그램이 요청하게되고, 이것은 운영체제의 프로세스 관리 규칙에 위배됨에따라 강제로 해당 프로그램이 종료되는 문제를 발생시킬 수 있습니다.

 

 

 

2. Delete 키워드에 대해서

사실 New 키워드에 대해서 자세히 다룬 덕분에 delete에 대해서는 크게 말씀드리게 없습니다.

 

단일 객체에 대한 할당을 제거할 시 delete를 사용하고, 배열 객체에 대한 할당 제서기 delete[]를 사용한다는 차이와 delete를 사용한 후에도 해당 영역에 대한 참조가 가능하지만, 실제로 접근하면 new키워드의 마지만 부분에서 언급했던 문제와 같은 일이 생길 수 있다는 것만 말씀드리면 될 듯합니다.

 

 

 


...더보기

해당내용은 사용하는 운영체제(OS)와 컴파일러, IDE에 따라서 차이가 있을 수 있는 내용입니다.

반응형
반응형

0. 시작하기

 

1. 동적할당이란?

C, Java와 같은 언어에 익숙하다면 동적할당이라는 개념에 대해서 익숙하실 수 있습니다. 하지만, Python과 같은 언어에 익숙하다면 많이 생소한 개념일 수 밖에 없습니다.

 

동적할당의 반대는 당연히 '정적할당'이라는 것이 될 것입니다.

 

정적할당은 C언어에서 변수의 크기를 정하고 해당 크기만큼을 메모리에 할당하는 것을 말합니다.

 

int x[20];

 

이라는 코드는 x라는 변수에 20개의 '정수'를 저장할 수 있는 배열공간을 할당한다는 의미입니다. 이것을 정적할당이라고 합니다. 선언과 동시에 변수의 크기를 정하기 때문입니다.(즉, 컴파일되는 과정에서 변수의 크기가 결정됩니다.)

 

int형은 기본적으로 4Byte의 크기를 갖기 때문에 x라는 배열은 80Byte의 공간을 할당받습니다.

 

여기서 문제가 생깁니다. 만약 저장할 데이터가 80Byte의 공간에 다 들어가지 못한다면 어떻게 할까요?

 

만약 80Byte의 공간에 그 이상의 데이터를 넣게되면 프로그램은 컴파일과정에서 [Out Of Index]라는 애러를 내뱉거나, 실행과정에서 [OverFlow]라는 끔찍한 상황을 만듭니다.

 

이러한 문제를 해결하기 위해 등장한 것이 '동적할당'입니다.

 

 

동적할당은 컴파일 과정에서 변수의 크기가 결정되는 것이 아니라, 프로그램이 실행되는 과정에서 필요한만큼 저장공간을 할당받는 것을 의미합니다. 이때 이용하는 데이터영역을 Heap이라고 합니다.

 

프로그램은 실행되는 과정에서 변수에 더 큰 공간을 할당할 필요가 생긴다면, 동적으로 Heap영역에 이 변수를 위한 공간을 마련합니다. 만약 Heap영역이 부족하게된다면, 운영체제로부터 더 많은 메모리에 대해나 할당을 요청하고 운영체제가 메모리를 해당 프로그램에 할당하면 해당영역을 Heap영역으로 지정하여 변수에 더 많은 공간을 할당합니다. 이것이 동적할당의 과정입니다.

 

만약 운영체제가 해당 프로그램이 요구하는 만큼의 메모리공간을 할당하지 못한다면 프로그램은 데이터처리에 문제가 생기고 이에따라 애러를 발생시킬 것입니다.( = Ram 용략이 작은 경우 메모리가 터져 게임이 튕기는 이유 )

 

 

 

2. 동적할당의 이용 ( CPP/NewDelete.cpp )

C언어에서는 문자열을 처리하기 위해서 동적할당을 많이 사용했습니다. 하지만, C++에선 string을 정식으로 제공하기 때문에 문자열처리에서 동적할당을 사용할일이 줄었습니다. 하지만, 동시에 클래스와 객체에 대한 개념이 생기면서 이러한 new와 delete 키워드를 사용할 일 자체는 훨씬 늘었다고 볼 수 있습니다.

 

아직 클래스에대해서 다루지 않았지만, 쉬운 예제를 통해 어떤식으로 new와 delete 키워드가 사용되는지 알아보겠습니다.

 

#include <iostream>
using namespace std;

class MyClass{
public:
    void out(){
        cout << "MyClass -> out" << endl;
    }
};

int main(){
    MyClass *cl1 = new MyClass;
    cl1->out();

    delete cl1;

    return 0;
}

실행결과

위에는 간단한 클래스와 클래스 메서드를 하나 만들고 메인함수에서 해당 클래스를 생성 및 메서드 호출의 과정을 담고있습니다.

 

어렵게 생각하지 말아주시고, main함수 부분에서 new를 이용하여 클래스를 생성하고 delete를 이용해서 생성한 클래스를 제거해주는 과정이라는 것만 알아주면 좋을 것 같습니다.

 

new와 delete 키워드는 아래와 같은 역할을 합니다.

 

  • new : Heap영역에 요청한 만큼의 동적공간을 할당합니다. (해당 공간의 주소를 반환합니다. -> 때문에 포인터로 받음)
  • delete : 할당받은 영역은 OS에게 반환합니다. (반환된 영역을 데이터를 참고하면 문제가 생길 수 있습니다.)

 

new와 delete 두 가지 모두 메모리에 공간을 할당/반환하는 역할을 하기 때문에 사용에 있어서 주의가 필요합니다.

 

다음 게시글에서 이 두 키워드에 대해서 조금더 알아보도록 하겠습니다.(중요~!)

반응형
반응형

0. 시작하기

1. Call-By-Value & Call-By-Reference란?

C언어에 익숙한 사람이라면, Call-By-Value와 Call-By-Address라는 것에 대해서 들어봤을 겁니다.

 

이것은 함수에 전달되는 인자가 '값(Value)'인지 '주소(Address)'인지에 따라서 결정됩니다.

 

그럼 Call-By-Reference는? 인자가 '참조자(Reference)'인 것을 의미합니다.

 

Address나 Reference나 작동방식에 있어서는 큰 차이가 없다고 생각합니다.

 

 

 

2. Call-By-Value ( CPP/CallByValue.cpp )

 

Call-By-Value는 일반적으로 많이 사용되는 방식입니다.

 

함수의 매개변수로 값이 전달되는 것을 의미하며, 보통 반환값이 존재하거나 전역변수와 같은 값을 통제하는데 사용하는 함수에서 사용합니다.

 

만약 Call-By-Value로 swap함수를 만든다면 어떤 문제가 발생하는지 알아보겠습니다.

 

예제코드를 아래와 같이 만들어보았습니다.

 

#include <iostream>
using namespace std;

void swap(int x, int y);

int main(void){
    int x = 10;
    int y = 20;

    swap(x, y);

    cout << x << " " << y << endl;
}

void swap(int a, int b){
    int temp = a;
    a = b;
    b = temp;

    cout << "a : " << a << "\nb : " << b << endl;
}

실행결과

 

실행해보면 swap이라는 함수 내에 전달된 x와 y값은 서로 위치가 바뀌어서 20, 10의 순서로 출력이 된것을 볼 수 있습니다.

 

하지만, main함수에서 변수 x, y를 출력해보면 함수에 들어가기 전과 동일한 10과 20이라는 값을 갖고있는 것을 볼 수 있습니다.

 

이것은 메인함수에서 swap 함수로 인자를 전달할 때, x와 y라는 변수의 값(10, 20 이라는 값)만을 전달한 것이기 때문입니다.

 

즉, 위의 코드에서 swap(x, y)는 swap(10, 20)과 동일한 작동을 한다는 것을 의미합니다. x와 y의 주소값등은 전달되지 않기 때문에 함수 내에서 아무리 값을 변경해도 결과적으로 x와 y의 값은 변함이 없다는 것을 말합니다.

 

이게 Call-By-Value : 값에 의한 호출을 말합니다.

 

 

 

3. Call-By-Reference ( CPP/CallByReference.cpp )

 

다음은 Call-By-Reference에 대해서 다뤄보겠습니다. 위에서 Call-By-Value를 다뤄봤기에 쉽게 추론하실 수 있을 것이라고 생각합니다.

 

예제코드를 먼저 보겠습니다.

 

#include <iostream>
using namespace std;

void swap(int &x, int &y);

int main(void){
    int x = 10;
    int y = 20;

    swap(x, y);

    cout << x << " " << y << endl;
}

void swap(int &a, int &b){
    int temp = a;
    a = b;
    b = temp;

    cout << "a : " << a << "\nb : " << b << endl;
}

실행결과

 

차이는 매개변수를 참조자로 선언해 주었다는 것만 존재합니다. 즉, Reference(참조자)를 이용하여 값을 '호출'하는 것이 아니라 실제 변수를 참조할 수 있는 참조자를 '호출'하는 것 이게 Call-By-Reference : 참조에 의한 호출을 의미합니다.

 

어렵지 않은 내용이라고 생각합니다.

 

 

 

 

4. 특이한 참조자의 사용 ( CPP/Reference4.cpp )

이제부터는 참조자의 여러가지 특이한 케이스들을 다뤄보겠습니다.

 

먼저 참조자는 선언시 참조할 '변수'를 지정해주어야 한다는 특징이 있습니다.

 

즉, 아래와 같은 코드를 수행하면 애러가 발생합니다.

 

#include <iostream>

int main(void){
    int &x = 20;

    return 0;
}

실행결과

 

참조자는 항상 변수를 참조하기 때문에 정수형 데이터를 참조자의 값으로 갖을 수 없다는 내용을 갖습니다.

 

그런데 const라는 녀석을 만나면 문제가 해결됩니다.

 

#include <iostream>

int main(void){
    const int &x = 10;

    return 0;
}

위와깉이 코드를 작성해서 수행하면 아무런 애러도 발생하지 않습니다.

 

왜? 그걸 위해서는 const함수가 무엇인지를 먼저 알아야 한다고 생각합니다.

 

  • const : constant의 약자로 '상수'를 정의하는데 사용합니다. '상수'란 코드내에서 절대로 변하지 않는 고정된 값을 의미합니다.

그럼 왜 const함수를 사용하면 애러가 없는걸까요? 이것은 여기서 10이 단순한 '값'이 아닌 특별한 '데이터'가 되었다는 것이 핵심입니다.

 

 

어려운 이야기일 수 있습니다. 풀어서 말하자면, 애러가 났던 코드에서 참조자는 10이라는 값을 전달받았지만, 아래의 참조자는 '메모리상에 상수 10이라는 값을 갖은 공간'을 전달받은 것을 의미한다고 생각하시면 좋겠습니다.

 

같은 10이라는 값이지만, 전자는 메모리에 존재하지 않는 10, 후자는 메모리에 상수로 지정된 10이라는 값

 

물론 후자의 경우, 특이하게도 '변수'가 없는 참조자 형태를 갖습니다. 때문에 어떻게보면 의도하지 않은 사용이 될 수도 있습니다.

 

하지만 덕분에 아래와 같은 코드를 사용도 가능해졌습니다.

 

#inlcude <iostream>
using namespace std;

int sub(const int &x, const int &y){
	return x-y;
}

int main(void){
	int result = sub(10,5);
    cout << result << endl;
    
    return 0;
}

 

 

 

5. 참조자의 문제점

편리한 참조자가 무슨 문제를 갖고있는가? 이것은 맨 처음에 다뤘던 swap코드를 다시 한번 보며 말씀드리겠습니다.

 

#include <iostream>
using namespace std;

void swap(int &x, int &y);

int main(void){
    int x = 10;
    int y = 20;

    swap(x, y);

    cout << x << " " << y << endl;
}

void swap(int &a, int &b){
    int temp = a;
    a = b;
    b = temp;

    cout << "a : " << a << "\nb : " << b << endl;
}

만약 위와같은 코드가 존재할 때, main함수 부분만은 보고 x, y 값이 [ Call-By-Reference ]라는 것을 알 수 있습니까?

 

swap함수를 정의한 것을 보지 않는다면, 메인함수에서는 해당 함수가 Call-By-Reference인지 Call-By-Value인지 알 수 없습니다.

 

큰 문제가 아니라고 생각할 수 있지만, 여러사람이 한 코드를 공유한다면 이것은 큰 문제가 될 수 있습니다.

 

당연히 Call-By-Value라고 생각하고 사용한 함수가 Call-By-Reference였고, 이에따라 함수의 인자로 전달한 값이 변하게되는 문제가 발생할 수 있습니다.

 

 

 

 

반응형
반응형

0. 시작하기

 

1. 포인터 ( CPP/Pointer.cpp )

참조자에 대해서 설명하기 위해서는 역시 포인터를 먼저 말하고 가야한다고 생각해서 적게되었습니다.

 

C언어를 입문하는 사람이 가장 힘들어하는 부분이 '포인터'라고 합니다.

 

포인터는 변수의 메모리상 주소를 가리키는 녀석을 말합니다.

 

조금 더 디테일하게 보겠습니다.

 

#include <iostream>
using namespace std;

int main(void){
    int a = 10;
    int *pt1 = &a;
    int **pt2 = &pt1;

    cout << a << endl;      // a의 값
    cout << &a << endl;     // a의 주소값

    cout << "\n---------------\n" << endl;

    cout << pt1 << endl;    // pt1의 값
    cout << *pt1 << endl;   // pt1이 가리키는 값
    cout << &pt1 << endl;   // pt1의 주소값

    cout << "\n---------------\n" << endl;

    cout << pt2 << endl;    // pt2의 값
    cout << *pt2 << endl;   // pt2가 가리키는 값
    cout << &pt2 << endl;   // pt2의 주소값

    return 0;
}

 

위와같은 코드를 하나 작성해보았습니다. 그리고 실행해보면 아래와 같은 결과를 볼 수 있습니다.

 

실행결과

 

 

이를 그림으로 표현하면 아래와 같습니다.

 

메모리상 데이터와 주소

어려워 보이는 개념일 수 있습니다만, 간단하게 생각해봅시다.

 

1개의 포인터(*)는 변수의 주소를 직접 갖고 있다는 의미입니다.

 

2개의 포인터(*)(=더블포인터)는 변수의 주소를 갖고있는 포인터의 주소를 갖고있다는 것을 말합니다.

 

포인터의 갯수가 3개, 4개가 되도 마찬가지입니다.(물론 3,4개씩 쓸일이 있을까 싶기는 합니다.)

 

C언어 기준으로 본다면, 포인터는 아래와 같습니다.

 

  • int *pt : 변수의 주소값을 저장할 'pt'라는 포인터를 만듭니다.
  • int **ptt : 변수의 주소값을 갖고있는 포인터의 주소값을 가리킬 (더블)포인터를 만듭니다.
  • print(*pt) : 포인터 pt가 가리키는 값을 출력합니다.
  • print(&pt) : 포인터 pt의 주소값을 출력합니다.

위의 4개만 알고있다면 포인터를 쉽게 사용은 못해도, 포인터를 이용한 다양한 알고리즘, 자료구조들을 이해하는데는 충분합니다.

 

 

2. 참조자 ( CPP/Reference1.cpp ~ Reference3.cpp )

그럼 본론으로 돌아가서 C++의 참조자에 대해서 언급해보겠습니다.

 

위에서 보았듯이 C++에서도 C언어와 마찬가지로 포인터(*)를 사용할 수 있는 것을 볼 수 있었습니다. 하지만, 프로그래밍을 할 때, 포인터를 사용한다는 것은 익숙하지 않은 프로그래머에게는 상당히 부담스러울 수 있는 일입니다.(난이도 때문)

 

이러한 문제 때문이었는지 C++에서는 '참조자'라는 특별한 친구를 만들어 주었습니다.

 

참조자는 변수에 대한 별명을 만들어주는 것이라 볼 수 있습니다.

 

예제 코드 몇개를 살펴 보겠습니다.

 

#include <iostream>   // CPP/Reference1.cpp
using namespace std;

int main(void){
    int x = 10;
    int &y = x;  // int y = x 사용시 결과는 11, 11

    x++;
    y++;

    cout << x << endl;
    cout << y << endl;

    return 0;
}

실행결과

 

만약 [ int y = x ]라고 해주었다면, 결과는 11, 11이 출력되었을 것입니다.

 

왜냐하면 변수라는 것은 [ 데이터 ]를 저장하는 공간이기 때문입니다. 데이터, 즉 값이라는 것을 저장하는 변수는 특정한 숫자나 특정한 문자열을 저장할 뿐이지 다른 변수의 주소값등을 저장하지는 못합니다.

 

따라서 위의 코드에서 [ int &y = x ]라는 부분이 특별한 기능을 한다는 것을 알 수 있습니다.

 

변수 선언과정에서 변수명 앞에 '&' 를 붙일 경우, 이것은 변수가 아닌 '참조자'라는 것으로 선언되었다는 의미를 갖습니다.

 

참조자는 위에서 언급한것과 같이 '변수'에 대한 '별명'을 붙여준다는 것을 말합니다.

 

위의 예제를 조금 수정해서 아래와 같이 실행해 보았습니다.

 

#include <iostream>  // CPP/Reference2.cpp

using namespace std;

int main(void){
    int x = 10;
    int &y = x;
    int &z = y;

    cout << &x << endl;
    cout << &y << endl;
    cout << &z << endl;

    return 0;
}

실행결과

위의 코드를 보게되면 '변수x'와 '참조자y'는 동일한 주소값을 갖고있는 것을 볼 수 있습니다.

 

그림으로 그려본다면 아래와 같은 모습임을 예측할 수 있습니다.

 

 

중요한것은 [변수x]와 [참조자 y, z]가 존재하는 형태라는 것입니다.

 

여러개의 변수가 하나의 데이터 공간을 공유하는 것은 아니라는 것을 꼭! 기억했으면 좋겠습니다.

( y 나 z가 없는 x는 존재할 수 있지만, x가 없는 y 또는 z 는 존재할 수 없습니다.)

 

 

참조자가 어떤 것인지는 대충 아셨을 것이라고 생각합니다. 그럼 이제 참조자가 어떤식으로 사용되는지 알아보겠습니다.

 

참조자의 가장 큰 특징은 딱 두가지입니다.

 

  • 참조자는 선언과 함께 참조할 변수가 정해져야 합니다. ( int &x; 와 같이 선언 불가)
  • 참조자는 함수 호출시 매개변수로 갖을 수 있다.

제가 설명력이 부족해서 이해하기 힘드실테니, 예제코드를 통해서 알아보겠습니다.

 

#include <iostream>

using namespace std;

void swap(int &a, int &b);

int main(void){
    int x = 10;
    int y = 20;

    swap(x,y);

    cout << x << endl;
    cout << y << endl;

    return 0;
}


void swap(int &a, int &b){
    int temp = a;
    a = b;
    b = temp;
}

실행결과

 

일반적으로 swap 함수를 사용할 경우 포인터를 사용해주었었습니다. 왜냐하면 함수의 경우 일반적으로 [Call-By-Value]를 원칙으로 하기 때문입니다.

( Call-By-Value, Call-By-Reference 에 대한 내용은 다음페이지에서 다루겠습니다. )

 

위의 swap 코드를 보게되면 [ int &a, int &b ]의 형태로 '참조자'를 이용한 것을 볼 수 있습니다.

 

따라서 이 swap 함수에서의 a, b라는 값은 메인함수의 [ x, y ]라는 변수의 '별명'이 되는 것을 볼 수 있습니다.

 

여기서 혹시 [ int &a ] 형태로 참조자는 선언할 수 없냐고 했는데 사용가능한건가? 하는 의문이 있을 수 있습니다.

 

결과는 당연히 가능합니다. 왜냐하면 함수의 매개변수는 함수가 코드내에서 실행되는 순간 할당되기 때문입니다.

 

위의 함수를 '인라인' 된것처럼 본다면 다음과 같은 코드가 될 것입니다.

 

#include <iostream>

using namespace std;

int main(void){
    int x = 10;
    int y = 20;

    // Swap 함수 영역 시작
    swap(int &a = x, int &b = y){
    	int temp = a;
        a = b;
        b = temp
    }
    // Swap 함수 영역 끝

    cout << x << endl;
    cout << y << endl;

    return 0;
}

'실제 코드가 아닌 추상적인 코드입니다'

 

 

위와같이 함수는 실행되는 과정에서 각각의 매개변수에 값을 할당하기 때문에 참조자만을 선언하는 것이 아니라는 것을 알 수 있습니다.

 

참조자는 정말 중요한 개념이고 다양한 응용이 있기 때문에 다음 페이지에서 조금 더 알아보도록 하겠습니다.

반응형

+ Recent posts