[TIL] 23-12-18

Lev·2023년 12월 18일
0

📓TIL Archive

목록 보기
6/33

printf (이어서)

calling convention : 함수호출규약

함수를 호출할 때 사용하는 규약

  • 일반적으로 컴파일러가 알아서 적절한 것으로 붙여준다.
  • 기계어로 함수를 호출하게 되면 호출자, 정리자, 인자 넘기기 등을 규약마다 작성해줘야 한다.
  • 전역함수는 대부분 __cdecl을 사용하는 편이다.
  • 추후에 배울 static 멤버함수는 __stdcall을 사용하고, 일반 멤버함수는 __thiscall(C++ 전용)을 사용한다.
  • 임의로 함수의 calll 방식을 변경할 수 있…긴 하지만 권장하지 않는다.

const

특정 메모리 영역의 값을 변경할 수 없다는 약속

void Damage(int& _Hp, int _Att)
{
	_Hp -= _Att;	// const가 없기 때문에 인자의 값 수정 가능
}

int StringCount(const char* _Ptr)
{
	int Count = 0;

	while (_Ptr[Count])
	{
		_Ptr[Count] = 'b'	// const가 있기 때문에 인자의 값 수정 불가능
		++Count;
	}

	return Count;
}
// 이 함수를 사용함으로써 인자로 들어간 문자열이 변경되어 나올 것이라고는 예상하지 않는다
// 따라서 인자에 const를 붙임으로써 사용자가 인자로 넣어준 값이 변경되지 않을 것이라고 명시한다 (1)

int main()
{
	// const가 붙지 않은 지역변수는 스택영역에 생성된다 (수정 가능)
	char Arr[10] = "aaaaa";
	// const가 붙은 지역변수는 컴파일러에 따라 상수가 될 수도, 스택영역에 만들어질 수도 있다 (수정 불가능)
	const char Arr1[10] = "aaaaa";

	const char* CPtr = Arr;	// const가 붙어있지 않은 참조형 자료형에 const 붙이기 => 가능
	char* NPtr = Arr;
	char* NPtr2 = CPtr;	// const가 붙어있는 참조형 자료형에서 const 떼기 => 불가능 (2)

	int Count = StringCount(Arr);
	Count = StringCount("AAAAA");	// 함수의 인자에 const가 없으면 불가능 (3)
	// "AAAAA"는 코드영역에 const char[6]의 형태로 존재하기 때문
	// 위치 : 50번지(라고 하자)
	// 크기 : 6byte
	// 형태 : const char[6]
	// 값 : AAAAA0

	int MonsterHp = 50;
	Damage(MonsterHp, 10);
}
  • 참조형(포인터, 레퍼런스)을 인자로 받는 함수에서, 참조형에 const를 걸어두지 않는다면 무조건 값이 수정되어서 나올 수 있음을 각오해야 한다. (1)
  • 참조형 자료형에는 const를 붙일 수도 안 붙일 수도 있지만, 한 번 붙은 const는 뗄 수 없다. (사실 const_cast라는 방법을 사용해서 뗄 수는 있지만, 사용하지 말자) (2)
  • 코드영역에 있는 것은 무조건 const이고, 값을 수정할 수 없다. 따라서 const를 사용하여 절대 값을 바꾸지 않겠다는 것을 명시해야 코드영역의 상수를 인자로 받을 수 있다. (3)
#include <iostream>

int main()
{
	{
		const int CValue = 0;
		int NValue = CValue;	// 가능 => 새로운 int형 변수를 생성한 것
	}
	{
		const int Value = 0;
		const int* CValue = &Value;
		int* NValue = CValue;	// 불가능
		/*
		Value
		위치 : 코드영역 100번지
		크기 : 4byte
		형태 : const int
		값 : 0

		CValue
		위치 : 스택영역
		크기 : 8byte
		형태 : const int*
		값 : 100번지

		NValue
		위치 : 스택영역
		크기 : 8byte
		형태 : int*
		값 : 100번지
		*/

		*CValue = 1000;	// 수정 불가능
		*NValue = 30;	// 수정 가능?!?!?
		// 수정할 수 없는 값인 Value에 접근한 NValue가 수정할 수 있는 형태이기 때문에 안되는 것
	}
	{
		const int Value = 2;
		const int* CValue = &Value;
		/*
		Value
		위치 : 코드영역 100번지
		크기 : 4byte
		형태 : const int
		값 : 2

		CValue
		위치 : 스택영역 600번지
		크기 : 8byte
		형태 : const int*
		값 : 100번지
		*/

		*CValue = 20;	// 불가능 => 100번지에 있는 2라는 값을 20으로 수정하겠다
		CValue = nullptr;	// 가능 => 600번지에 있는 100번지라는 값을 0번지로 수정하겠다

		const int* const CCValue = &Value;
		// const int : 100번지의 값을 수정할 수 없다
		// *const : 600번지의 값도 수정할 수 없다
		// 크게 의미는 없다...

		CCValue = nullptr;	// 불가능
	}
	{
		char Arr[100] = "Test Printf";
		
		printf_s(Arr);
		strcpy_s(Arr, 100, "aaaaa");

		// 함수에 마우스를 대보면...
		// const char* => 함수가 실행되어도 인자의 값은 변하지 않는다
		// char* => 함수가 실행되면 인자의 값이 변할 수 있다
	}
}

char

문자 표현 방식

  • 본질적으로 글자 = 2진수로 표현된 정수
  • e.g. a, b, c, d, e, ㄱ, ㄴ, ㄷ, ㄹ, ㅁ ⇒ 인간이 보기 편하도록 디스플레이에 보여주는 것일 뿐
  • char를 사용하면 C++가 운영체제에게 ‘이건 글자이니 글자의 형태로 보여줘야 해’라고 명령
char Ch = 'A';	// 65

// char를 int로 변경하는 방법
int iNumber = '1' - '0';	// 49 - 48 = 1

variable argument : 가변인자

인자의 개수를 정하지 않고, 내가 인자를 넣어주는 순간 해당 인자 수를 가진 함수를 만드는 것

  • 함수의 인자는 일반적으로 내가 정해준 개수만큼만 넣을 수 있다.
  • 정해진 개수보다 적게, 또는 많이 넣으면 에러가 난다.
#include <iostream>

int StringCount(const char* _Ptr)
{
	int Count = 0;

	while (_Ptr[Count])
	{
		++Count;
	}

	return Count;
}

void MyPrintf(const char* const _Format)
{
	int Number = StringCount(_Format);	// 3

	for (int i = 0; i < Number; i++)
	{
		char Ch = _Format[i];
		putchar(Ch);
	}
}

void VarTest(...)
{

}

void VarCountCheck(int Count, ...)	// (1)
{
	int* Ptr = &Count;

	for (int i = 0; i < Count; i++)
	{
		// 포인터의 더하기 => 현재위치 + sizeof(자료형) * 넣어준 정수
		Ptr += 2;	// 8byte 이동 (다음 인자)
		int Value = *Ptr;
	}
}

int main()
{
	char Arr[10] = { 'a', 'a', 'a', 0, 'a', 'a', 'a', 0 };

	printf_s(Arr);	// "aaa"
	MyPrintf(Arr);	// "aaa"

	printf_s("aaa", 312, 312, 312, 312, 312, 312, 312, 312, 312);
	// printf_s 함수는 인자를 엄청 많이 받을 수 있다!?
	
	VarTest(10, 20);
	// void VarTest(int Count0, int Count1) {}
	// 호출 시에 위와 같은 함수를 만들어내는 것
	
	VarCountCheck(4, 1, 2, 3, 4);	// (1)

	VarCountCheck(8, 1, 2, 3, 4);	// (2)
	// 이런식으로 실수할 가능성이 있기 때문에 위험하다
	int* ptr = nullptr;
	VarCountCheck(8, 1, ptr, 3, 4);	// (2)
	// 심지어 int가 아닌 값도 인자로 들어갈 수 있어 더 위험하다

	printf_s("%d count %d", 999, 123);	// "999 count 123"
	// 하지만, printf_s 함수는 가변인자를 포함하고 있다
}
  • 가변인자는 일반적인 함수의 인자처럼 접근할 수 없다.
  • 그래서 대개 첫번째 인자를 뒤에 따라오는 인자의 개수로 명시하고, 포인터를 이용해 접근한다. (1)
  • 하지만 쓰레기값이 들어가 있는 알 수 없는 메모리, 또는 수정하면 안되는 메모리에 접근할 가능성이 있어 매우 위험하다. (2)

과제

// Q) StringToNumber()과 NumberToString()을 완성시키자

#include <iostream>

int StringCount(const char* _Ptr)
{
	int Count = 0;

	while (_Ptr[Count])
	{
		++Count;
	}

	return Count;
}

int StringToNumber(const char* const _NumberString)	// string을 int로 변경하는 함수
{
	int Cnt = StringCount(_NumberString);
	char Ch = _NumberString[0];
	int Sum = 0;
	int Dec = 1;

	for (int i = Cnt; i > 0; i--)
	{
		Ch = _NumberString[i - 1];
		Sum += (Ch - '0') * Dec;
		Dec *= 10;
	}

	return Sum;
    
    /*
    int Count = StringCount(_NumberString);
    int Value = 1;

    // pow 함수를 대체하기 위해
    for (int i = 0; i < Count - 1; i++)
    {
        Value *= 10;
    }

    int Result = 0;
    for (int i = 0; i < Count; i++)
    {
        char Ch = _NumberString[i];
        int Number = Ch - '0';
        Result += Number * Value;
        Value /= 10;
    }

    return Result;
    */
}

void NumberToString(int _Number, char* _Ptr)	// int를 string으로 변경하는 함수
{
	int Cnt = 0;

	for (int num = _Number; num > 0; num /= 10)
	{
		Cnt += 1;
	}
	
	for (int i = Cnt; i > 0; i--)
	{
		_Ptr[i - 1] = (_Number % 10) + '0';
		_Number /= 10;
	}
    
    /*
	// 어떤 함수든 원본값을 보존해두는 것이 좋다!!!
	int CalNumber = _Number;
    const char* CPtr = _Ptr;

    int NumberCount = 0;
    while (CalNumber)
    {
        CalNumber /= 10;
        ++NumberCount;
    }

    int Mul = 1;
    for (int i = 0; i < NumberCount - 1; i++)
    {
        Mul *= 10;
    }

    int Value = 0;
    CalNumber = _Number;

    for (int i = 0; i < NumberCount; i++)
    {
        Value = CalNumber / Mul;
        _Ptr[i] = Value + '0';
        CalNumber -= Value * Mul;
        Mul /= 10;
    }
	*/
}

int main()
{
	int Number = StringToNumber("32");	// 32
	
	char Arr[100] = {};
	NumberToString(123123, Arr);	// "123123"
}

📢 코딩스탠다드
가급적 유지하자!

  1. 전역변수와 지역변수는 이름을 구분하여 작성하자
  2. 변수에는 반드시 초기값을 설정하자
  3. 함수의 인자 앞에는 언더바를 붙이자
  4. 포인터를 초기화할 때 절대 0을 사용하지 말고, nullptr을 사용하자
  5. 경로, 함수명, 변수명에 한글은 절대 쓰지 말자
  6. 프로젝트 생성 시 바탕화면은 절대 피하자
  7. if문을 사용할 때, 한 줄 코드일지라도 반드시 중괄호를 사용하자
  8. 함수의 리턴값을 꼭 변수로 받아서 확인해보자 (new!)
    e.g. printf_s의 리턴값은 출력된 문자의 갯수(int)이다.
profile
⋆꙳⊹⋰ 𓇼⋆ 𝑻𝑰𝑳 𝑨𝑹𝑪𝑯𝑰𝑽𝑬 ⸝·⸝⋆꙳⊹⋰

0개의 댓글