[C++ 기초] Printf 구현해보기, 문자 응용 함수 만들어보기, 포인터 이해

라멘커비·2023년 12월 19일
1

CPP 입문

목록 보기
6/25
post-thumbnail

Ctrl + K + F : VS 자동 줄바꿈

MyPrintf 만들어보기

저번에 배운 가변인자 등의 개념과 저번에 만든 NumberToString함수를 활용해서 printf함수를 만들 수 있다.

가변인자의 주소로 넘어가는 방법

함수의 인자들의 주소(위치)는 8바이트씩 차이난다. 그 점을 이용해서 가변인자를 사용할 수 있다.

__int64 Address = reinterpret_cast<__int64>(&_Format);
const int* Ptr = reinterpret_cast<int*>(Address);
Ptr += 2;
__int64 Address = reinterpret_cast<__int64>(&_Format);
Address += 8;
const int* Ptr = reinterpret_cast<int*>(Address);

이외에도 다양할 수 있다.

MyPrintf 구현 (형식지정자 %d 가능)

#include <iostream>

void NumberToString(int Number, char* _Ptr) {
	//어떤 함수든 원본값을 보존해 놓는게 좋습니다.
	int CalNumber = Number;
	int NumberCount = 0;
	const char* CPtr = _Ptr;

	//정수가 몇자리인지 알아야 합니다.
	//10나누기를 합니다.

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

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

	//숫자를 분해하기 시작
	//0 나누기가 허용되지 않는다.
	int Value = 0;
	CalNumber = Number;
	for (int i = 0; i < NumberCount; i++) {
		Value = CalNumber / power;
		_Ptr[i] = Value + '0';
		CalNumber -= Value * power;
		power /= 10;
	}
}

int MyPrintf(const char* const _Format, ...) {
	int Count = 0;
	__int64 Address = reinterpret_cast<__int64>(&_Format);
	while (_Format[Count]) {
		char Ch = _Format[Count];
		// 포맷팅이 아닐경우 글자를 출력하고 다시 반복한다.
		if (Ch != '%') {
			putchar(Ch);
			Count++;
			continue;
		}
		// 포맷팅 문자인 %를 만났을 경우.
		char NextCh = _Format[Count + 1];
		switch (NextCh) {
		case 'd':
		{
			// 인자를 끌어내야 합니다.
			//int* Ptr = reinterpret_cast<int*>(&_Format);	// 1. const char* const인데 값을 바꿀수있게되어버리므로 안됨 
															// 2. 바로 주소를 포인터로 바꾸는 것은 C스타일임

			Address += 8;
			const int* Ptr = reinterpret_cast<int*>(Address);

			char Arr[100] = {};
			NumberToString(*Ptr, Arr);

			int Index = 0;
			while (Arr[Index]) {
				putchar(Arr[Index]);
				Count++;
				Index++;
			}
			break;
		}
		default:
		{
			int a = 0;
			break;
		}
		}
	}
	return Count;
}

int main()
{
	int Return = MyPrintf("aaabbb %d", 123); //aaabbb 123

	return 0;
}
  • MyPrintf 함수 실행하는 메모리 모양새(?)
    스택 영역에 Main > MyPrintf 순서로 쌓인다.

(개인)다중 포인터 이해


하나씩 차근차근 가리키는 다중 포인터에 대해서는 이해했다. 그런데 이해할 수 없는 부분이 생겼다.

⚒️강제로 int***형으로 변환시킨 경우 *Ptr, **ptr, ***Ptr은 각각 어딜 가리키지??

    __int64 Value = 1;
    __int64*** Ptr = reinterpret_cast<__int64***>(&Value);

확인해보기위한 코드.

    __int64** Ptr2 = *Ptr;
    __int64* Ptr3 = **Ptr;
    __int64 Ptr4 = ***Ptr;

예외가 발생한다.

**Ptr 예외***Ptr 예외

*Ptr에 Value의 1이 들어있다. **Ptr에는 주소 1이 가리키는 곳의 값이 있지만 램의 주소 1번지는 접근하는 의미가 없으므로 거의 null같은 느낌이다. ***ptr는 또 (**ptr)번지의 값을 가리킨다. 1이 아닌 다른 주소를 넣어도 비슷하게 무의미하다.

결론 : Ptr에 Value의 주소가 들어가있고, *Ptr에 바로 1이 있다. **Ptr, ***Ptr에 각각 쓰레기값이 들어간 것이 아니라, 포인터로서 차근차근 앞을 가리키고 있기는 하다. 하지만 주소를 강제로 다중포인터로 바꾼 것은 거의 아무데나 가리키는 수준이므로 의미없는 행동이다.


문제

CharConvert함수 만들기

#include <iostream>

int CharConvert(char* _String, char _PrevCh, char _NextCh)
{
    // 바뀐 글자수를 리턴합니다.
    int count = 0; // 바뀐 글자수 세기
    int index = 0; // 인덱스
    while (_String[index]) {
        if (_String[index] == _PrevCh) {
            _String[index] = _NextCh;
            count++;
        }
        index++;
    }

    return count;
}

int main()
{
    char Arr[10] = "aaabbbccb";

    int Result = CharConvert(Arr, 'b', 'd'); //두번째 문자를 세번째 문자로 바꾸기.
    // "aaadddccc"

    return 0;
}

LeftMoveString 함수 만들기

선생님 코드(더깔끔)

// _Start번째부터 전부다 왼쪽으로 한칸이동
void LeftMoveString2(char* _Ptr, int _Start)
{
    // 이것도 방어 코드
    if (_Ptr == nullptr) {
        return;
    }
    int Count = StringCount(_Ptr);
    // 방어 코드
    if (_Start == 0) {
        _Start = 1;
    }

    for (int i = _Start - 1; i < Count; i++) {
        _Ptr[i] = _Ptr[i + 1];
    }
}

내코드

// _Start번째부터 전부다 왼쪽으로 한칸이동
void LeftMoveString(char* _Ptr, int _Start)
{
    char NewArr[100] = {};
    int Count = StringCount(_Ptr);

    for (int i = 0; i < _Start - 1; i++) {
        NewArr[i] = _Ptr[i];
    }

    for (int i = _Start - 1; i < Count; i++) {
        NewArr[i] = _Ptr[i + 1];
    }

    // 전체 복사해주기
    for (int i = 0; i < Count; i++) {
        _Ptr[i] = NewArr[i];
    }
}

DeleteChar 함수 만들기

// _DeleteCh를 지우기
void DeleteChar(char* _Ptr, char _DeleteCh)
{
    int Count = StringCount(_Ptr);
    for (int i = 0; i < Count; i++) {
        if (_Ptr[i] == _DeleteCh) {
            LeftMoveString(_Ptr, i + 1);
            i--; // 왼쪽으로 옮기고 나면 글자수 전체가 줄기 때문에 i를 같이 땡겨줘야함.
        }
    }

}

방어 코드

내가 알지 못하는 메모리를 침범하려고 하는 코드 등이 있을 때, 그런 코드를 막기 위해서 사용하는 것을 방어 코드라고 부른다.

기초

  • 함수에 배열 넣기
    함수에 배열을 인자로 넘기면 암시적으로 형변환이 된다. 포인터로 바뀐다.
int ArrayEx[99];
Function(ArrayEx);
  • 문자열 자르고 합치고 머시기 하는 것을 파싱이라고 함
    나중에 개발에 필요해서 이렇게 생긴 것들을 파싱해야 함
    // "Fighter_Ang_Status"

    // "{
    // <FighterStatus>
    //      <Att>10</Att>
    //      <Def>10</Def>
    // </FighterStatus>
    // }"
}
  • 문자열 비교할 때
//    Ptr : 500번지, "ABC" : 100번지 라고 할 때
 char Ptr[10] = "ABC";
 if (Ptr == "ABC") { // false -> 주소값 때문 (500번지와 100번지 비교하는 것)

 }

주소를 비교하는 것이기 때문에 Ptr과 "ABC"는 같지 않다. 그래서 strcmp라는 문자열을 비교해주는 함수가 따로 있다.

strlen 문자열 길이
strcmp 문자열 비교
strcpy_s 문자열 복사

숙제

int를 char*로 변환했는데 4바이트가 1바이트씩 들어가있을것으로 예상했으나 거꾸로 쪼개져(?) 있다....... 값을 애초부터 뒤집혀서 넣는다.
(사실은 이게 뒤집힌 게 아니라 정상적인 거다.)
바이트의 순서가 우리 눈에 뒤집혀 보이는 이 현상의 이름을 알아오세요..

        // 숙제 이 현상을 뭐라고 부를까요?
        // 정수의 바이트가 뒤집혀서 보이는 현상.
        //          0b00000000 00000000 00000000 00000001
        int Value = 0b00000000000000000000000000000001;
        //          0b00010001 00000010 00000000 00000000  이렇게 들어감

        char* Ptr = reinterpret_cast<char*>(&Value);

        //             Ptr[0]   Ptr[1]   Ptr[2]   Ptr[3]
        // char*    0b 00000001 00001100 00000000 00000000
        
        char Test0 = Ptr[0];
        char Test1 = Ptr[1];
        char Test2 = Ptr[2];
        char Test3 = Ptr[3];
profile
일단 시작해보자

0개의 댓글

관련 채용 정보