입출력 스트림 버퍼


입출력이란?
입력과 출력을 의미한다.
프로그래밍에서 입력이란?
-> 외부의 데이터가 프로그램 내부로 흘러 들어오는 것.

프로그래밍에서 출력이란?
-> 프로그램 내부에서 외부로 데이터가 흘러 나가는 것.


스트림이란?
프로그래밍에서 스트림이란 통로라는 의미로 사용된다.
-> 가상의 통로이다.

버퍼란? 
스트림 중간에 있는 메모리 블록
데이터 송수신의 효율성 때문에 존재한다.

표준 스트림
stdin : 표준 입력 스트림. 기본 키보드 대상
stdout: 표준 출력 스트림. 기본 모니터 대상
stderr: 표준 오류 스트림. 기본 모니터 대상 // 사용하지 않을 것

 

단일 문자 출력 함수

fputc()

int_Character : 출력할 문자를 전달

FILE* _Stream : 어떤 스트림을 이용할 것인지 전달

fputc('A', stdout);

 

단일 문자 입력 함수

fgetc()

char ch = fgetc(stdin); // 입력
fputc(ch, stdout);      // 출력

여기서 abc라고 입력했을 때 출력되는 문자는 a만 나오게 된다.

이유는 간단하게 단일 문자 입출력 함수는 단일 문자만 입/출력이 가능하기 때문이다.

 

단일 문자 입출력 함수가 반환하는 값

-> 함수 호출이 성공하면 아스키코드의 정수 값을 반환한다.

-> 실패 시 EOF라는 것을 반환한다.

 

EOF란

-> End Of File의 약자.
-> 파일의 끝을 표현하기 위해 정의해 놓았다!
-> 더 이상 읽을 데이터가 없을 경우 EOF 반환하여 읽기를 끝낸다.
-> EOF는 -1의 값을 가진다.

 

반환 타입이 char가 아닌 int인 이유
-> char 자료형은 시스템 환경에 따라 unsigned로 표현될 수 있다.
-> char가 unsigned로 표현되면 -1인 EOF를 표현할 수 없게 된다. 
-> 하지만, int 자료형은 어떤 환경에서든 signed를 유지한다.
-> EOF를 표현할 수 있다.

'Programming > C++ Basic' 카테고리의 다른 글

C++ 열거체(enum)  (0) 2020.09.06
C++ 경로  (0) 2020.09.02
C++ 동적 할당(C, C++기반), malloc, calloc  (0) 2020.09.01
C++ 구조체와 포인터  (0) 2020.08.31
C++ 구조체(Struct)  (0) 2020.08.31

동적 할당

-> 메모리 영역 중 Heap 영역에 저장된다.

-> 사용자가 원할때 메모리에 등록시키고, 사용자가 원할때 메모리에서 해제할 수 있다.

-> 단, 동적 할당한 메모리를 해제하지 않을 경우 메모리 누수가 발생한다.

-> 프로그램을 종료해도 메모리에 계속해서 상주한다.

-> 동적 할당은 C방식과 C++방식으로 나눠진다.

 

C 기반 동적 할당

char*	pName = nullptr;
pName = Func();
cout << pName << endl;

pName은 Func함수가 반환하는 값을 저장한다.

-> Func함수에는 szBuff라는 지역 변수가 있다.

-> Func함수가 반환하는 값은 해당 지역 변수의 할당 주소이다.

-> Func함수가 종료되면 stack 영역을 정리하면서 지역 변수의 할당 메모리 영역을 소멸시킨다.

-> 결과적으로 pName은 소멸될 공간의 주소를 받아오는 상황이다.

 

동적 할당 방법

#1 malloc()

-> void* : 시작 주소를 반환한다.

-> 단, 해당 시작 주소는 몇 바이트만큼 어떤 형식으로 읽을지 모르는 상황이다.

-> 이를 정상적으로 사용하기 위해서는 형 변환을 진행해야 한다.

malloc 함수를 선언하고 마우스를 올려보면 (size_t Size)가 보인다.

size_t _Size는 Heap 영역에 얼마만큼 메모리 공간을 할당할지 byte단위로 크기를 전달한다.

 

malloc(4)라고 선언하면 Heap 영역에 4byte만큼 공간을 할당한다는 의미이다.

 

malloc 함수 형 변환

(int*)malloc(4);

-> 할당한 공간의 주소를 반환할 때 int*형으로 형 변환해서 반환

 

int* ptr = (int*)malloc(4);

cout << "ptr: " << ptr << endl;		// Heap 영역의 주소
cout << "*ptr: " << *ptr << endl;	// heap 영역의 주소에 할당한 값

Heap영역에 4bytes만큼 할당 후 해당 공간의 시작 주소를 반환하는데,
int*형으로 변환하여 ptr이라는 변수에 저장한다.

 

동적할당 해제 

-> 동적할당을 해제 할 때는 free() 함수를 사용하면 된다.

그런데 동적할당을 해제 했음에도 주소 값이 그대로 있다.

-> 이유는 동적 할당된 메모리를 해제는 완료하였지만 주소 값은 그대로 남아있는 상황이 발생해서 그렇다.

-> 이것을 Dangling Pointer라고 한다.

 

Dangling Pointer(위험한 주소)

-> 동적할당을 해제한다해도 변수의 주소는 그대로 남아 있다.

 

Dangling Pointer가 발생하는 경우.

-> #1 할당되지 않은 메모리 공간의 접근

-> #2 다른 변수에서 동적할당한 곳이 같은 곳을 할당 받을 경우

 

Dangling Pointer 예방 방법

-> free 함수를 호출 후 변수를 nullptr로 초기화하는 습관을 들여야한다.

calloc()
-> size_t _Count : 몇 개를 할당할 것인지 전달.
-> 동적 배열을 만들 때 사용한다.

 

4bytes 공간을 3개 할당(동적 배열) 

calloc(3, 4); 

calloc으로 동적 배열 만들기

 

int iSize = 0;

cin >> iSize;

int*	pArr = (int*)calloc(iSize, sizeof(int));

for (int i = 0; i < iSize; ++i)
	cout << pArr[i] << endl;

malloc으로 동적 배열 만들기

int iSize = 0;

cin >> iSize;

int*	pArr = (int*)malloc(sizeof(int) * iSize);

for (int i = 0; i < iSize; ++i)
		cout << pArr[i] << endl;

malloc과 calloc의 차이점
-> malloc은 쓰레기 값으로 초기화
-> calloc은 0으로 초기화가 진행된다.

 

지금까지 한 것은 C기반의 동적 할당이고,

이번에는 C++기반의 동적할당을 해보자.

 

C++ 기반의 동적 할당

new 연산자

사용 예)

new 자료형

int* ptr = new int;

Heap 영역에 자료형의 크기만큼 공간을 할당하고,

자료형의 포인터형으로 주소를 형 변환하여 반환한다.

 

동적 할당 해제

delete

delete ptr;
ptr = nullptr

C기반은 포인터를 초기화 하지 않고 동적 할당을 해제하면 주소 값이 그대로 남지만,

C++ 기반은 다른 곳에서 동적 할당할 경우에 같은 주소를 참조하지 않게 다른 곳으로 주소가 바뀐다.

이 경우 역참조를 이용하여 데이터를 다룰 경우 오류가 발생한다.

 

C++ 기반 역시 Dangling Pointer 때문에 동적 할당을 해제하고나서 포인터를 nullptr로 초기화 해줘야 한다.

 

동적할당과 동시에 초기화

-> C++ 기반은 동적 할당과 동시에 초기화가 가능하다.

int* ptr = new int(10);

C기반의 초기화

int a = 10;

C++기반의 초기화

int a(10);

 

C++기반의 동적 배열

int iSize = 0;
cin >> iSize;

int* ptr = new int[iSize];

동적 배열 해제 방법

ptr 변수로만은 Heap 영역에 할당된 공간이 배열인지 아닌지 알 수 없는 상황이다.
-> 컴파일러는 단순하게 자료형을 토대로 판단한다.

// 주소만 남기고 삭제
delete
ptr = nullptr;

// 전체 삭제
delete[] 
ptr = nullptr;

// 동적 배열 삭제
delete[] (ptr);
ptr = nullptr;

 

모든 메모리 할당은 직접 구현한 New, Delete 키워드를 통해 호출하는 것이 좋다.

'Programming > C++ Basic' 카테고리의 다른 글

C++ 경로  (0) 2020.09.02
C++ 입출력 스트림 버퍼, 단일 문자 입/출력 함수  (0) 2020.09.02
C++ 구조체와 포인터  (0) 2020.08.31
C++ 구조체(Struct)  (0) 2020.08.31
C++ 문자열 함수  (0) 2020.08.31

구조체와 포인터

-> 구조체와 같이 연속된 메모리에 멤버 변수들이 나열된다.

-> 단, 연속된 메모리를 사용한다고해서 구조체의 이름또한 포인터라고 착각하면 안된다.

 

 

구조체 포인터 선언

typedef struct tagInfo
{
	char	chName[16];
	int		iKor;
	int		iEng;
	int		iMath;
	int		iTotal;
	float	fAver;
}INFO;

여기서 포인터 선언을 할 때 pInfo.chName에 접근이 불가능한 이유는

.(dot)연산자는 구조체에 의한 연산자이지 포인터에 의한 연산자가 아니기 때문이다.

 

멤버 접근 포인터 연산자(->)

->는 역참조 연산자와 .(dot) 연산자를 합친 것과 같다.

 

구조체의 크기

-> 구조체의 크기는 멤버 젼수들의 크기를 합한 것이 아니다.

#1. 멤버 변수들을 모두 펼친다.

typedef struct tagSize
{
	char	chSize;		// 1
	int	iSize;	        // 4
	float	fSize;		// 4
	double	dSize;		// 8
}SIZE;

#2. 크기가 가장 큰 멤버 변수를 찾는다. -> 기준

double	dSize;		// 8

#3. 기준 만큼 메모리를 할당하고, 순차적으로 채워 넣는다.

chSize + iSize = 5

-> 8byte의 메모리를 할당한 상태

 

#4. 채워 넣을 수 없을 경우 추가 할당을 한 후 다시 채워 넣는다.

chSize + iSize + fSize = 9가 나오기 때문에 추가 할당

-> 16byte의 메모리를 할당한 상태

 

fSize + dSize = 12byte가 나오기 때문에 추가 할당

-> 24byte의 메모리를 할당한 상태

 

#5. 모두 채워 넣은 후 합산한 결과가 최종 크기가 된다.

8byte 기준 : chSize(1byte) + iSize(4byte)  = 5byte

8byte 기준 : fSize  = 4byte

8byte 기준 : dSize = 8byte

 

최종크기 = 24byte 

 

이 때, 빈 공간이 생기게 되는데 이를 padding byte 라고 한다.

 

padding byte를 최소화 하는 것이 메모리 절약에 좋기 때문에

멤버로 문자 배열을 선언할 경우 2의 N승으로 만드는 습관을 들이는 것이 좋다.

 

구조체 멤버로 구조체를 가질 경우의 크기

-> 두 구조체를 모두 펼친 후 크기가 가장 큰 멤버 변수가 기준이 된다.

typedef struct tagSize
{
	char	chSize;		// 1
	int	iSize;		// 4
	float	fSize;		// 4
	double	dSize;		// 8
}SIZE;

typedef struct tagSize1
{
	int  	   iSize;  // 4
	float	   fSize;  // 4
	long longll Size;  // 8
	tagSize    tSize;  // 24
}SIZE1;

-> 기준을 토대로 각 구조체의 크기를 따로 계산한다.

-> 이후, 합산한 결과가 최종 크기가 된다.

최종 크기 = 40byte 

 

자기 자신을 멤버로 가지는 구조체

-> 자기 자신을 멤버로 가질 경우에는 재정의 된 이름이 아닌 본연의 이름을 가져야만 한다.

-> 컴파일 단계에서 구조체의 크기를 계산하기 위해 멤버 구조체를 펼지게 된다.

-> 하지만 펼쳐도 구조체가 또 존재하기 때문에 계속해서 무한루프를 돌게된다.

(무한루프 + 사이즈를 계산할 수 없음.)

-> 이 경우를 예방하기 위해서 포인터 형으로 선언을 해야한다.

-> 포인터는 어떤 자료형이든 고정된 크기(4byte)를 가지기 때문이다.

 

 

 

 

구조체

-> 사용자 정의 자료형이다.

-> int, float, double 같은 자료형들은 기본 자료형이다.

-> 사용자 정의 자료형이라고 해서 몇 byte 크기만큼 어떤식으로 읽을 수 있게 만들 수 없다.

-> 기존에 있던 자료형들을 모아서 데이터 덩어리 형태로 만든다.

-> 데이터 집합이라고 생각하면된다.

 

구조체의 형태

struct 자료형이름
	{	// 여기부터

		기본 자료형 + 변수명
		기본 자료형 + 변수명
		기본 자료형 + 변수명

	}

구조체 변수 선언

struct 자료형	변수명;
struct tagInfo	tInfo;

 

구조체의 멤버 접근

-> 구조체의 멤버에 접근하는 방법은 연산자를 사용한다.

.(dot) 연산자

struct tagInfo			tInfo = { "AAA", 10, 20, 30 };
cout << tInfo.szName << endl;
cout << tInfo.iKor << endl;
cout << tInfo.iEng << endl;
cout << tInfo.iMath << endl;
cout << tInfo.iTotal << endl;
cout << tInfo.fAver << endl;

 

구조체의 멤버 변수 대입

구조체의 멤버 변수의 값을 변경하려고 하는데 정수 값은 바뀌지만 문자는 바뀌지 않는다. 왜 그럴까?

이유는 문자 배열이기 때문에 즉, 주소를 다루기 때문이다.

 

문자 배열은 상수이기 때문에 일반 형식으로 대입이 불가능하다.

 

하지만 strcpy_s를 사용으로 변경은 가능하다.

 

typedef

-> type(타입) define(정의하다)

-> 자료형을 다른 이름으로 재 정의 한다.

-> 자료형에 별명을 붙여서 사용한다는 의미.

 

장점

-> 긴 자료형을 줄여서 사용할 수 있다.

-> 유지보수가 좋다.

예를 들어 short 자료형을 int 자료형으로 바꾸는 작업을 할 때 다음과 같이 편리하게 할 수 있다.

typedef		자료형	별명
typedef		short 	_short;

_short	iB = 20;
typedef		자료형  별명
typedef		int 	   _int;

_int	iB = 20;

 

C구조체와 C++구조체의 차이점
-> struct 키워드 내부에 typedef이 정의되어 있는지 여부
-> C에서는 struct tagInfo 라고 자료형을 지어야 한다.
-> C++에서는 tagInfo로 자료형을 지을 수 있다.

 

struct나 class에서 초기화 값 변경을 바꾸려고 const 멤버 변수를 쓰지 않는다.

참조(&) 멤버변수의 경우도 마찬가지

'Programming > C++ Basic' 카테고리의 다른 글

C++ 동적 할당(C, C++기반), malloc, calloc  (0) 2020.09.01
C++ 구조체와 포인터  (0) 2020.08.31
C++ 문자열 함수  (0) 2020.08.31
C++ 문자 배열과 문자열 상수  (0) 2020.08.28
C++ 2차원 배열  (0) 2020.08.28

+ Recent posts