[C++ 기초] printf 함수 뜯어보기 - const, 가변인자

라멘커비·2023년 12월 18일
0

CPP 입문

목록 보기
5/25
post-thumbnail

printf_s 함수 더 알아보기

넣어준 글자를 출력해주는 함수.
printf는 C에서 사용하던 출력 함수다. 내부 내용이 C스타일로 짜여져 있다.

  • extern "C" : 이부분을 컴파일 C스타일로 컴파일 해줘.

  • inline : 치환(해줄 수 있니?라는 느낌). 컴파일러의 판단하에 이 함수의 내용을 그대로 복사해서 넣을 수 있다면 이 함수가 실행된 위치에 코드를 복사하고 실행시킴. (가장 빠른 프로그램은 main에 모든 코드를 집어넣은 프로그램이다라는 농담이 있다고 함)

  • int : 리턴값

  • __cdecl : 함수호출규약, 우리가 함수 만들 때도 자동으로 붙음(생략되어서 안 보임)

    • 함수라는 것은 CPU에 요청되는 것이고 스택메모리를 사용한다. 함수호출규약은 컴파일러가 알아서 붙여준다. 기계어로 함수를 호출하면 호출자와 정리자 인자 넘기기 등을 직접 다 해줘야 하는데 그럴 때 함수호출규약마다 따로 쳐줘야한다.

    • 전역함수는 대부분 __cdecl을 사용한다. static 멤버함수는 __stdcall을 사용하고 일반 멤버함수는 __thiscall(c++전용)을 사용한다. 억지로 함수의 call 방식을 바꿀 수 있지만 그걸 바꾸면 문제가 생길 수 있다.

    • int main() {
       	Test();
       	return 0;
      }

      Test함수의 caller(호출자)는 main이다.

    • https://learn.microsoft.com/ko-kr/cpp/cpp/cdecl?view=msvc-170

  • printf_s : 이름

  • const char* _Format, : 전달한 문자열 인자를 const로 받기 때문에 변형될 일 없다.

  • ... : 가변인자

printf_s의 리턴값
printf_s는 화면에 출력되는 문자열의 길이를 반환한다.

const

  • const : 특정 메모리영역의 값을 변결할 수 없다.
    const로 선언하는 메모리의 영역은 비트적 상수성을 지킨다. 이후에 값을 변경할 수 없다.
    컴파일러의 옵션에따라 아예 상수로 대체될 수도 있다.
const int Value = 0; // 4바이트의 영역에서 1비트도 고칠 수 없다.

이 변수의 값을 변경할 수 없다.

  • 함수를 만들 때 암묵적인 합의로 const가 없으면 값이 바뀌어서 나올 수 있다는 것으로 생각한다.

참조형(포인터나 레퍼런스)을 인자로 받는 함수가 참조형에 const를 안 걸어놨다면 무조건 값이 수정되어서 나올 수 있다는 의미로 받아들인다.

예를 들어 아래 StringCount함수는 글자 수를 세는 함수이지만 원본의 값을 바꿀 수 있다.(X)
const가 붙어있지 않으면 값이 바뀌어 나온다는 것을 암묵적으로 의미한다. 하지만 바뀌지 않아야 의도에 맞기 때문에 const를 붙임으로써 사용자가 넣어준 값이 바뀌지 않을 것이라는 것을 명시해준 것이다.

// XXXXXXXX
int StringCount(char* _Ptr) {
    int Count = 0;
    while (_Ptr[Count]) {
        _Ptr[Count] = 'b';
        ++Count;
    }
    return Count;
}

// OOOOOOOO
int StringCount(const char* _Ptr) {
    int Count = 0;
    while (_Ptr[Count]) {
        ++Count;
    }
    return Count;
}
  • 다음과 같이 const가 붙지 않은 포인터나 참조형을 사용하는 함수에 상수를 넣어주려고 하면 에러가 난다.
    "aaaaaa"는 코드영역에 상수로서 존재하게 되기 때문에 const char[6]의 형태를 넣어주는 것이 된다. "aaaaaa"를 넣을 수 있게 하려면 함수의 인자에 const를 붙여줘야 한다.
const 없음const 있음
  • 지역변수 중에서 const가 붙은 애는 컴파일러에 따라서 상수가 될 수도 있고 스택에 만들어질 수도 있다. (const가 안 붙은 지역변수는 스택에 만들어짐)

const ex

  • const 없는 참조형 자료형에 const를 붙여주는건 가능하지만, const 붙어있는 참조형 자료형에서 const를 떼는 것은 불가능하다. (const_cast라는 특별한 방법을 사용해서 뗄 수 있지만 선생님은 절대로 절대로 사용하지 않으신다고 함)

  • int* 자료형에 const를 붙여 사용해보았다.

    그럼 600번지라는 것을 수정못하게 하려면 어떻게 해야할까?

    뒤에 const를 붙이면 된다.

char 추가 설명

문자 표현 방식
abcde ㄱㄴㄷㄹ <- 인간이 보기 편하게 만든 디스플레이적인 내용일뿐이다.
글자에 대한 모든 권한은 OS가 갖고 있다.
우리가 char을 사용한다고 하면 c++이 운영체제에게 "이거 글자야" 라고 알린다.
8비트로 이루어진 램 안의 특정 메모리 영역을 글자로 바꿔주는 것이다.
8비트의 메모리 상태는 결국 2진수일뿐이다. 그러므로 char는 곧 숫자(정수)라고 할 수 있다.

가변인자

함수의 인자는 일반적으로 내가 정해준 개수만큼만 넣어줄 수 있고 내가 정한 개수보다 많게 넣어도 에러, 적게 넣어도 에러다.
가변인자란 인자의 개수를 정하지 않고 사용하는 함수 인자이다. 내가 인자를 넣어주는 순간 이 함수를 지우고 그 인자 개수만큼의 함수를 만든다.

void VarTest(...) {

}

함수 매개변수들은 8바이트씩 떨어져있다. 이유는 C++의 어떤 자료형도 8바이트보다 크지 않기 때문이다. (그렇다고 4바이트짜리 int형 인자의 크기가 8바이트가 된다는 건 아니다.) 첫번째 인자의 주소와 넣은 인자의 개수만 알면 주소를 모두 알 수 있다.
그래서 가변인자의 값을 이렇게 사용할 수 있다.

void VarCountCheck(int Count, ...) {
    int* Ptr = &Count;
    // 포인터의 더하기는 내 현재위치 + sizeof(자료형) * 넣어준 정수.
    for (int i = 0; i < Count; i++) {
        Ptr += 2;	// 8바이트 이동한 것임.
        int Value = *Ptr;
    }
}

그런데 인자의 개수를 사용자가 직접 입력하게 하는 것은 위험하다는 한계점이 있다.
가변인자에 nullptr가 들어가는 등의 위험성도 있다.

printf_s("%d count %d", 999); 	//가변인자의 개수를 맞추지 못하면 
								//두번째 %d에는 의도치않은 메모리 영역을 읽어오게 된다.

이제 printf함수를 직접 만들 수 있다. (내일)


우리반 코딩 스탠다드

  • 전역변수와 지역변수 이름 무조건 다르게 하기. ex) GlobalA, LocalA
  • 무조건 초기값을 설정한다.
  • 함수의 인자에는 앞에 언더바_를 붙인다.
  • 포인터를 초기화할 때 0을 사용하지 않는다.
  • 경로 및 함수 변수 한글 절대 금지
  • 바탕화면에 프로젝트 절대 금지 있으면 평가에 반영하겠음
  • 중괄호 위치 신경 안 씀, 중괄호 생략은 X (한줄로치는거 X)
  • const_cast 절대 절대 사용 X.

수업시간 과제

StringToNumber함수와 NumberToString함수 만들기.

#include <iostream>

int StringCount(const char* const _Ptr)
{
    int Count = 0;
    while (_Ptr[Count])
    {
        ++Count;
    }
    return Count;
}

void NumberToString(int Number, char* _Ptr) {
    int count = 0;
    int num = Number;
    // count 구하기
    // 맨 앞에 0이 들어가면 count가 잘못됨!!!!!!!!!!!
    // 맨 앞에 0이 붙으면 8진수라서 그럼... 10진수만 생각하면 될듯?
    while (num) {
        num /= 10;
        count++;
    }
    // 뒷자리부터 채우기
    //while (Number) {
    //    int ch = Number % 10;
    //    _Ptr[count - 1] = ch + '0';
    //    count--;
    //    Number /= 10;
    //}
    // while문으로 돌면 맨 앞이 0이면 끝나버림
    for (int i = count - 1; i >= 0; i--) {
        int ch = Number % 10;
        _Ptr[i] = ch + '0';
        Number /= 10;
    }
}


int StringToNumber(const char* const _NumberString)
{
    // 글자 개수를 알아내야 합니다.
    // StringCount();
    // * 10 써야 합니다.
    // / 10 써야 합니다.
    // = 변수 - '0' 사용.
    // for문을 사용해야합니다.
    // 예외처리 안합니다.

    // char Ch = _NumberString[0];

    int result = 0;
    int count = StringCount(_NumberString);

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

    for (int i = 0; i < count; i++) {
        int ch = _NumberString[i] - '0';
        power /= 10;
        result += ch * power;
    }

    return result;
}

int main()
{
    int Number = StringToNumber("123454321"); // 문자열이 그대로 숫자가 되어 나오는 함수를 만들면 됨
    char Arr[100] = {};
    NumberToString(1023450, Arr);
    return 0;
}

선생님의 NumberToString 코드

void NumberToString2(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; j++) {
        power *= 10;
    }

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

0개의 댓글

관련 채용 정보