윈도우 환경에서 운영체제 설치시간을 알아보자.

포렌식 업무에 종사하거나 공부하는 사람들이라면 이미 누구나 아는 내용일테지만 새삼스럽게 정리하는 이유는 포렌식을 처음 공부하는 사람들에게 조금이나마 도움이 되길 바라는 마음 때문이다. 의외로 학회나 세미나를 참석해보면 다 알고 있을 것이라고 생각했던 내용을 모르고 있는 경우가 종종 있어서 앞으로 간단하게나마 정리해보려고 한다.

 

운영체제 설치 시간의 포렌식적 의미

우선 포렌식적으로 운영체제 설치 시간을 파악하는 것이 무슨 도움이 될까?

우선 용의자는 자신의 증거 은폐를 위해 포맷이나 완전삭제(wiping) 후 운영체제를 새로 설치할 수 있다. 하지만 자신의 은폐사실을 숨기는 경우가 있다. 실제 현장에서도 운영체제를 새로 설치했음에도 불구하고 사건 시점 이전부터 계속 사용해왔다고 우기는 사람이 종종 있다고 한다.

다른 예는 현재 파일시스템의 모든 파일은 운영체제 설치 시간 이후의 시간을 가져야 한다. 하지만 시간 정보가 운영체제 설치 시간보다 이전이라면 용의자가 의도적으로 수정을 했을 가능성이 있다. 하지만, 다른 장소(다른 저장매체 등)에서 이미 생성된 파일을 이동 및 복사한다면 생성 시간은 운영체제 시간보다 전으로 설정되어 있을 것이다.

이외에도 운영체제 설치 시간을 파악한다는 것은 시스템을 조사하는데 많은 참고가 될 수 있다.

 

윈도우 설치 시간 확인

윈도우 설치 시간을 확인하는 방법은 레지스트리를 이용하거나 WMI(Windows Management Instrumentation)를 이용해 확인할 수 있다.

 

1. 레지스트리를 이용한 확인

레지스트리의 다음 키 값을 확인하면 윈도우 설치 시간을 확인할 수 있다.

Windows 98/ME :

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FirstInstallDateTime

Windows NT/XP/Vista/7 :

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\InstallDate

시간은 유닉스 시간(Unix Timestamp) 형식으로 되어 있다. 위 시간을 유닉스 시간으로 변경하면 다음과 같다.

InstallDate Value: 4B0B3BA0

Unix Timestamp : 2009년 11월 24일 10:49:20

 

2. WMI – Win32_OperatingSystem

WMI의 Win32_OperatingSystem 클래스를 확인하면 InstallDate를 확인할 수 있다.

http://msdn.microsoft.com/en-us/library/aa394239(VS.85).aspx

WMI를 쉽게 확인하는 방법은 윈도우에 기본으로 내장되어 있는 wmic.exe 를 활용하는 방법이다.

시작 -> 실행에서 “wmic”를 치면 WMI를 사용할 수 있는 명령창이 뜬다. 

wmic -> os get installdate [엔터]

앞서 살펴본 레지스트리의 값과 동일하게 나온다. WMI는 윈도우 내부에 존재하는 데이터베이스에 질의하여 정보를 얻어온다.
문제는 레지스트리 값은 손쉽게 변경시킬 수 있다는 점이다. 그러면 WMI 시간은 변경되지 않는 것인가?

레지스트리의 값을 0xd2029649(1234567890)로 변경한 후 wmic를 이용해 시간을 확인해 보았다.

이처럼 레지스트리를 변경하면 WMI도 함께 변하게 된다. 윈도우 설치 시간을 사용자가 수정을 했다면 수정한 사실은 어떻게 알 수 있을까? 여러 가지 방법이 있겠지만 파일시스템이 NTFS인 경우 NTFS 포맷 시간을 기반으로 알아 볼 수 있다. NTFS 포맷 시간은 다음 포스팅에서 살펴보자.

 

대용량 소켓 서버 프로그래밍이라는 주제로 글 하나 올린 것이 화근이 되어서 해당 검색어를 통해서 제 블로그를 방문하시는 분들이 간혹 계시는 지라, 책임감을 느끼고 포스트를 이어 갑니다.  (이럴 틈이 없이 바쁜데 ㅠ.ㅠ)


오늘은 시리즈를 염두하면서 첫 번째 주제로 "Atomic Operation"에 대해서 이야기하고자 합니다.  "Atomic Operation"을 먼저 이야기하려는 이유는, 대용량 지원을 위해서는 멀티 스레드 프로그래밍이 필수이기 때문입니다.  그리고, 멀티 스레드 프로그래밍을 효율적으로 작성하기 위해서 가장 큰 적은 역시 "Context Switching 비용"입니다. 아!!  어려워 보이는 용어가 시작부터 난무하고 있습니다 ㅠ.ㅠ


우선, "Atomic Operation"을 쉽고 무식하게 설명하자면, "한 번에 실행 할 수 있는 명령어"라고 할 수 있습니다.


[소스 1]

1.var
2.a, b : integer;
3.begin
4.a := 1;
5.b := a + 1;

위의 소스 코드를 보면 4: 라인의 경우 한 줄의 코드이기 때문에 한 번에 실행 될 것이라고 착각할 수도 있습니다.  하지만, 이것이 실제 CPU가 처리 할 때에는 [그림 1]에서처럼 여러 명령어를 순차적으로 실행해야 같은 결과를 얻을 수가 있습니다.



[그림 1]


결국 멀티 스레드 상황에서 해당 코드를 실행하기 위해서는 임계영역(Critical Section 등)을 이용해서 락을 걸어두고 실행을 마친 후 락을 푸는 작업으로 한 번에 하나의 스레드만이 코드를 실행 할 수 있도록 해야 합니다.  이때, 스레드가 락에 걸리고 풀리는 과정 중에 수반되는 작업량이 무시 못할 정도로 크며, 이로 인해서 성능 저하가 발생합니다.  이것을 "Context Switching 비용"이라고 합니다.


"b := a + 1;"과 같은 코드가 간단하고 대수롭지 않게 보일지 모르지만, 멀티 스레드 상황에서는 아주 중요한 코드가 됩니다.  이때문에 각 플랫폼 마다 해당 코드를 한 번에 실행 할 수 있는 장치를 마련하고 있습니다.  윈도우즈에서는 Interlokced 함수들이 그런 기능을 제공합니다.


[소스 2]

01.var
02.Old : TObject;
03.iIndex, iMod : integer;
04.begin
05.FCurrent := AObj;
06. 
07.iIndex := InterlockedIncrement(FIndex);
08.if iIndex >= FRingSize then begin
09.iMod := iIndex mod FRingSize;
10.InterlockedCompareExchange(FIndex, iMod, iIndex);
11.end else begin
12.iMod := iIndex;
13.end;

[소스 2]는 대용량 소켓 서버를 작성하면서 패킷 풀을 만들기 위해서 작성한 코드의 일부분입니다.  링 버퍼에 새로운 객체를 추가하기 위해서 현재 위치(FIndex)를 한 칸 앞으로 전진하고 있습니다.  얼핏 보아도 여러 줄의 코드가 실행되고 있기 때문에 임계영역을 이용하여 락을 걸어야 할 것처럼 보입니다.  하지만, Interlocked 함수들을 이용해서 락을 사용하지 않고 현재 위치를 변경하고 있습니다.


7: InterlockedIncrement를 이용해서 FIndex에 1을 더하고 결과를 iIndex에도 저장합니다.  이 동작은 한 번에 이루어지기 때문에 멀티 스레드 상황에서 안전합니다.  이후 다른 스레드가 FIndex의 값을 변경하더라도 우리는 iIndex를 참조하고 있기 때문에 우리가 얻은 값이 중간에 변경 될 염려가 없습니다.


8-9: iIndex가 링 버퍼의 크기를 넘어서게 되면, 링 버퍼 크기로 나눠서 나머지를 취해야 합니다.  (크기가 정해진 큐를 작성하는 알고리즘 참고)


10: FIndex를 계속 증가시켜면 데이터 타입의 한계로 인해서 에러가 발생합니다.  따라서, iMod 값으로 바꿔야 합니다.  하지만, 지금 FIndex를 바꾼다면 다른 스레드가 이미 바꾼 값을 다시 되돌려 놓는 등의 문제가 발생합니다.  따라서, InterlockedCompareExchange를 이용해서 FIndex의 값이 iIndex와 같다면 아직 다른 스레드가 FIndex를 변경하지 않았으므로 iMod를 대입합니다.  이 과정 역시 한 번에 실행됩니다.


이후에는 iMod를 이용해서 "FRing[iMod]"과 같이 데이터를 접근 할 수가 있습니다.  또한 이 모든 과정이 락을 걸지 않고도 멀티 스레드에서 안전하게 동작함을 확신 할 수가 있습니다.


[소스 2]에서 생각 할 수 있는 의문점은 "링 버퍼가 한 바퀴 돌았을 때, 이전 메모리 해제를 어떻게 하는 가?"입니다.  우선 실제 코드는 [소스 3]과 같으며, 전체 코드는 http://bit.ly/XWBvBJ 를 참고하시기 바랍니다.


[소스 3]

1.Old := InterlockedExchangePointer(Pointer(FRing[iMod]), AObj);
2.if Old <> nil then Old.Free;

1: InterlockedExchangePointer를 이용해서 한 번에 "FRing[iMod] := AObj;"를 대입하고, 원래의 값은 Old에 대입하게 됩니다.


2: 만약, Old가 nil이 아니면 객체를 파괴합니다.


그런데 이 Old를 다른 스레드가 사용하고 있으면 어떻게 될까요?  큰 일 납니다 ㅠ.ㅠ  따라서, 저는 충분히 큰 크기의 링 버퍼를 구현하여 모든 객체의 라이프 사이클이 스레드의 참조 기간보다 길다는 것을 보장합니다.  그렇게 할 수 없는 곳에서는 사용 할 수 없지만, 일반적인 패킷 교환 등에서는 그런 일이 벌어지지 않습니다.


또한, 위의 코드는 혹시라도 그러한 일이 벌어지면 "Access Violation" 에러가 발생하게 되지만, 실제 코드에서는 좀 더 보강한 방법을 통해서 컨넥션을 종료하는 것으로 구현되어 있습니다.  그토록 오랫 동안 패킷을 처리 못했다면 분명 문제가 있는 접속이기 때문입니다.  해당 코드에 대해서는 다음 시리즈를 기약해 보도록 하겠습니다 ㅡ.ㅡ;;  (실제 패킷 풀에서는 메모리를 해제하지 않고 재사용하며, 잘못 된 재사용을 막을 수 있도록 코드가 추가되어 있습니다)



Lock-Free에 대한 참고 자료


 


 

통상 Critical Section을 처리하는데 Lock을 많이 사용한다.

그런데 성능이 굉장히 중요한 부분에 있어서 Lock을 사용하기에는 성능 저하가 우려 되는 경우가 있다.


이럴 때 활용할 수 있는 것이 Atomic 연산자를 이용한 Critical Section처리이다.

Atomic 연산이란 기본적으로 연산 수행 중 thread 전환이 되지 않는 것을 보장하는 연산을 일컫는다.


흔히 하는 실수로 성능 때문에 lock을 사용하지 않고 flag 변수를 하나 두고,

flag 변수를 이용하여 critical section을  처리할려 하는데,  이 경우 flag변수에 대한 읽고 쓰는 연산이 

atomic 하지 않으면 critical section이 제대로 보호 받지 못한다.


예를 들어 아래와 같이 코딩을 하여 critical section 처리를 한다면...


unsigned int semaphore  = 0;

while (semaphore) {

    // flag 풀릴 때 까지 yield하면서 기다림

}

semaphore  = 1;

// critical section 처리 함

semaphore  = 0;


위의 코드를 수행하는 여러 thread에서 semaphore flag변수가 0임을 동시에 확인 하면

여러 thread가 동시에 critical section에 진입하게 되어 문제를 일으키게 된다.


이를 해결하기 위해서는 semaphore변수가 0이면 바로 1로 바꿔 버리는 연산이 atomic하게 thread 전환없이

처리되어야 한다.


unsigned int swap(unsigned _new)

{

   unsigned int old = semaphore;

   semaphore = _new;

   return old;

}

while (swap(1)) {

       // flag 풀릴 때 까지 yield하면서 기다림

}

// critical 처리함

swap(0);


위의 코드에서 swap() 함수가 atomic 하게 처리되면 critical section 보호가 된다.


Windows의 경우는 <boost/detail/interlocked.hpp> 에서 이러한 atomic operation들이 지원되어 이를 활용하면 된다.

안타깝게 windows가 아닐 경우는 "Interlocked intrinsics not available" 이라는 에러 메세지가 출력된다.


Linux의 경우는 x86 계열 CPU에서는 gcc에서 제공하는 __exchanged_and_add를 사용할 수 있다.

이 __exchange_and_add의 경우 gcc 4.0.2 이전에는 

arm-none-linux-gnueabi/include/c++/4.0.2/bits/atomicity.h

이후에는 아래와 같이 있다.

arm-none-linux-gnueabi/include/c++/4.5.1/ext/atomicity.h


최근에 Android 폰등에서 ARM CPU를 많이 사용하는데 위의 __exchange_and_add() 이 컴파일은 되기는 하나,

정작 테스트해보면 atomic하지 않아 critical section보호가 제대로 되지 않거나,

아니면 일반적인 pthread mutex로 구현되어 성능이 좋지 않은 것을 확인하였다.


ARMv6 이상 CPU에서는 kernel쪽을 뒤져 보면 아래와 같은 atomic operation inline assembly함수를 찾을 수 있다.

inline unsigned long testAndSwap(volatile int *ptr, unsigned long old, unsigned long  _new)

{

    unsigned long oldval, res;

    do {

        __asm__ __volatile__("@ testAndSwap\n"

        "ldrex  %1, [%2]\n"

        "mov    %0, #0\n"

        "teq    %1, %3\n"

        "strexeq %0, %4, [%2]\n"

            : "=&r" (res), "=&r" (oldval)

            : "r" (ptr), "Ir" (old), "r" (_new)

            : "cc");

    } while (res);

    return oldval;

}


ARMv5te 이하의 CPU의 경우에는 multi-core를 지원하지 않는 CPU로 kernel쪽에서는 

단순히 interrupt를 disable시킨 상태에서 flag 변수 test and swap으로 구현되어 있는데,  

이러한 방식은 user space에서는 활용할 수 없다.


그런데 swap assembly command가 지원되어서 이를 이용하여 swap 함수를 만들어서 활용할 수 있다.

inline unsigned long swap(volatile int *ptr, unsigned long _new)

{

      unsigned long oldval;

        __asm__ __volatile__("@ swap\n"

        "swp %0, %1, [%2]\n"

            : "=&r" (oldval)

            : "r" (_new), "r" (ptr) );

        return oldval;

}


ARM Cortext-A8 CPU에서 간단히 구현해서 pthread_mutex_trylock()과 성능 비교를 해보면 

위의 atomic 연사자를 이용한 critical section 보호가 4배이상 빠른 것을 확인할 수 있었다.


그러나 성능 최적화에서 무엇보다도 중요한 것은 Profiling을 먼저 해보고 

어떤 SW 부분이 bottleneck인지 확인하고 성능 최적화에 들어가야 한다.


성능에 크게 문제가 되지 않을 경우에는 표준적인 pthread_mutex_trylock()등을 활용하면

critical section진입을 위해서 yield하면서 busy waiting하지 않고 suspend되고 resume되는 이점이 있어서

전체적으로 성능상에 오히려 이점이 될 수도 있다.


 


C++11 atomic을 이용해서 프로그래밍할 때 어떻게 해야 하는지 맛 보기 위해 간단한 예제를 작성해 봤습니다. multiple thread에서 공유 integer 변수를 loop 한번 당 매번 1씩 경쟁적으로 증가시키는 간단한 프로그램입니다.


일반 integer 변수와 atomic integer 변수로 비교해 보았고, 일반 integer 변수의 경우 역시 예상대로 정확한 결과가 나오지 않았고, atomic integer 변수는 정확한 결과가 나왔습니다.


또, 일반 integer 변수는 상당히 빠르게 수행되지만 atomic integer 변수는 상당히 느리다는 것을 확인할 수 있었습니다.


테스트 프로그램은 다음과 같습니다. C++11 프로그램인 만큼 몇 가지 C++11 스타일을 적용했습니다


#include <iostream>

#include <atomic>

#include <vector>

// C++11 <thread>가 있으나 버그가 있어 사용하지 못하고 

// boost::thread 사용

#include <boost/thread.hpp>


using namespace std;

using boost::thread;


enum {

    NTHREADS = 8,      // Core i7 Desktop이라 8로 설정

    NRUN = 10000000,

    NLOOP = NRUN * 10

};


// atomic integer type. atomic<int>라고도 할 수 있음. 

// atomic하게 증가됨

atomic_int an{0};

int n{0};


void increment_atomic(int id, int nloop) {

    cout << __PRETTY_FUNCTION__ 

         << "(" << id << ") starts" << endl;

    while (nloop-- > 0) {

        // relaxed memory ordering.

 // 단순 카운트인 경우에 충분

        an.fetch_add(1, memory_order_relaxed);

        // an.fetch_add(1, memory_order_seq_cst);

        // or ++an; 일반 integer처럼 prefix ++ 지원

 // 이 때 memory ordering은 memory_order_seq_cst

        if (nloop % NRUN == 0)

            cout << __PRETTY_FUNCTION__ 

                 << "(" << id << ") running..." << endl;

    }

    cout << __PRETTY_FUNCTION__ 

         << "(" << id << ") exits" << endl;

}


void increment_int(int id, int nloop) {

    cout << __PRETTY_FUNCTION__ 

         << "(" << id << ") starts" << endl;

    while (nloop-- > 0) {

        ++n;

        if (nloop % NRUN == 0)

            cout << __PRETTY_FUNCTION__ 

                 << "(" << id << ") running..." << endl;

    }

    cout << __PRETTY_FUNCTION__ 

         << "(" << id << ") exits" << endl;

}


void test_func(void (*incr)(int, int), void (*print)()) {

    vector<thread> thrs{};


    cout << "Creating threads" << endl;

    for (int i = 0; i < NTHREADS; ++i) {

        // 개선된 push_back()이라 할 수 있는 

        // emplace_back() 활용

        thrs.emplace_back(thread(incr, i, NLOOP));

    }


    cout << "Waiting for all threads to terminate" << endl;

    // lambda 함수 사용

    for_each(thrs.begin(), thrs.end(), [](thread& thr) {

        thr.join();

    });


    print();

}


void usage() {

    cout << "Usage:" << endl;

    cout << "test-atomic atomic|int" << endl;

}


int main(int argc, char* argv[]) {

    if (argc == 2)     {

        string type{argv[1]};


        if (type == "atomic")         {

            // lambda 함수 사용

            test_func(increment_atomic, []() {

                cout << "atomic int = " << an << endl;

            });

        }

        else if (type == "int")         {

            // lambda 함수 사용

            test_func(increment_int, []() {

                cout << "int = " << n << endl;

            });

        }

        else         {

            usage();

        }

    }

    else     {

        usage();

    }

}


컴파일 및 실행 환경은 다음과 같습니다.

$ uname -a
Linux ubuntu 3.2.0-26-generic #41-Ubuntu SMP Thu Jun 14 17:49:24 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux
$ g++ --version
g++-4.6.real (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
Copyright (C) 2011 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

boost thread와 컴파일할 때는 다음과 같이 library를 명시해 줘야 합니다.

$ g++ --std=c++0x -o test-atomic test_atomic.cpp -lboost_thread-mt

실행한 결과는 다음과 같습니다.

$ ./test-atomic
Usage:
test-atomic atomic|int
$ time ./test-atomic atomic
Creating threads
void increment_atomic(int, int)(void increment_atomic(int, int)(void increment_atomic(int, int)(void increment_atomic(int, int)void increment_atomic(int, int)((24) starts) starts1
) starts
3) starts
0) starts

void increment_atomic(int, int)(6) starts
Waiting for all threads to terminate
void increment_atomic(int, int)(7) starts
void increment_atomic(int, int)(5) starts
void increment_atomic(int, int)(6) running...
void increment_atomic(int, int)(3) running...
void increment_atomic(int, int)(5) running...
void increment_atomic(int, int)(0) running...
void increment_atomic(int, int)(1) running...
void increment_atomic(int, int)(7) running...
void increment_atomic(int, int)(2) running...
void increment_atomic(int, int)(4) running...
void increment_atomic(int, int)(5) running...
...
...
void increment_atomic(int, int)(0) exits
void increment_atomic(int, int)(7) running...
void increment_atomic(int, int)(7) exits
void increment_atomic(int, int)(4) running...
void increment_atomic(int, int)(4) exits
void increment_atomic(int, int)(6) running...
void increment_atomic(int, int)(6) exits
void increment_atomic(int, int)(1) running...
void increment_atomic(int, int)(1) exits
void increment_atomic(int, int)(2) running...
void increment_atomic(int, int)(2) exits
atomic int = 800000000

real 0m14.613s
user 1m40.774s
sys 0m0.032s

$ time ./test-atomic int
Creating threads
void increment_int(int, int)(1) starts
void increment_int(int, int)(2) starts
void increment_int(int, int)(0) starts
void increment_int(int, int)(3) starts
void increment_int(int, int)(4) starts
void increment_int(int, int)(5) starts
Waiting for all threads to terminate
void increment_int(int, int)(6) starts
void increment_int(int, int)(7) starts
void increment_int(int, int)(4) running...
void increment_int(int, int)(1) running...
void increment_int(int, int)(7) running...
void increment_int(int, int)(2) running...
void increment_int(int, int)(4) running...
void increment_int(int, int)(5) running...
void increment_int(int, int)(6) running...
...
...
void increment_int(int, int)(3) running...
void increment_int(int, int)(2) running...
void increment_int(int, int)(2) exits
void increment_int(int, int)(0) running...
void increment_int(int, int)(0) exits
void increment_int(int, int)(6) running...
void increment_int(int, int)(3) running...
void increment_int(int, int)(6) running...
void increment_int(int, int)(3) running...
void increment_int(int, int)(3) exits
void increment_int(int, int)(6) running...
void increment_int(int, int)(6) exits
int = 117287057

real 0m2.518s
user 0m16.345s
sys 0m0.004s

atomic type 을 이용하여 lock-less data structure를 구현할 수 있다고 하더군요.


%04x - 빈자리에 0이 딸려오는 4자리 hex값



다 알겠지만,


%x는 int형 변수를 16진수로 표시해주는 것이다.


%4x는 16진수를 표시하되 4자리를 맞춰서(오른쪽정렬) 표시해준다.


아마 여기까진 다들 알지도 모른다.



하지만 %04x는 알까 'ㄱ')?



0이 붙으면 빈자리를 공백 대신에 0으로 채워준다.


예를 들면 0x23은 0023이 되는 것이다.


이것은 %d나 %u에도 똑같이 적용된다.






%hc, %hs - wprintf에서 멀티바이트 문자(열) 출력

%lc, %ls - printf에서 유니코드 문자(열) 출력



wprintf에서 일반 멀티바이트 문자나 스트링을 출력하려면 앞에다가 h만 붙여주면 된다.

반대로 printf에서 유니코드 문자나 스트링을 출력하려면 앞에다가 l만 붙여주면 된다.





예)

setlocale(LC_ALL, "");


wprintf (L"%hs%hc\n", "엿", '!');

printf ("%ls%lc\n", L"엿", L'!');


출력결과)


엿!
엿!





%p - pointer



포인터 출력하는데 %x붙이지마라.

진짜 출력하는 방법은 %p다.

win64든 뭐든 어디서든 잘 출력된다.

니네들 이거 알아야되는데, 다들 모른다.





%I64d, %I64u, %I64x - 64-bit 정수 출력시



64비트 정수(__int64) 출력은 I64를 붙여쓰면 된다.


%I64d, %I64u, %I64x처럼...


물론 I64에서 I는 I(아이)다!

l(엘)이 아니고...





%Iu, %Id, %Ix - ULONG_PTR



ULONG_PTRLONG_PTR, and DWORD_PTR are numeric types that are as wide as a pointer. In other words, they map to ULONGLONG, and DWORD respectively on Win32, and ULONGLONGLONGLONG, andULONGLONG on Win64.

The I size prefix (capital-i, not lowercase-L) is what you need to print *LONG_PTR on Win32 and Win64.





해석하기 귀찮아

이거 필요하신 분 정도면 이정도 해석은 가능하겠지







%*d - 숫자가 출력될 너비를 런타임에서 조절



%2d나 %5d와 같은 너비 크기 조절을 런타임에서 하고자 할때, *를 넣으면 된다.

%*d를 쓰면 인자를 두개를 사용한다. 처음 인자가 너비이고 두번째 인자는 원래 수이다.




다음은 적절한 트리 출력 예제이다.



 void Tree::Print(Node* pNode, int level) {     if (NULL != pNode)     {         Print(pNode->Left, level+1);         printf("%*d%s\n", 2 * level, pNode->Key);         Print(pNode->Right, level+1);     } }



%.*s - substring!



str함수를 이용해야 하는 문자열 잘라내기를 printf내에서 할 수 있다!

그리고, 길이만 안다면 NULL로 끝나지 않는 문자열에 대해서도 잘라낼 수 있다.



printf("%.*s\n", sublen, str)



이렇게 하면, str의 왼쪽에서 sublen만큼만 출력할 수 있다.



s앞에 .을 붙이면, .뒤에 숫자를 붙임으로써 문자열이 출력되는 갯수를 정할 수 있다.

(예: %.5s는 문자열이 몇개가 오든 5개만 출력한다는 소리)



그런데 s앞에 *도 붙이면, .의 뒤에 와야할 숫자를 printf의 인자로 줄 수 있다.



그래서 마치 printf를 substring함수와 같이 쓸 수 있는 것이다.




예)

int i;

char* str = "1234567890";

for (i = 0; i <= 10; i++)

{

printf ("%.*sㅗ", 10-i,str+i);

printf ("%.*s\n", i,str);

}



출력결과)


1234567890ㅗ

234567890ㅗ1

34567890ㅗ12

4567890ㅗ123

567890ㅗ1234

67890ㅗ12345

7890ㅗ123456

890ㅗ1234567

90ㅗ12345678

0ㅗ123456789

ㅗ1234567890





추가로, printf의 인자로 직접 정한 숫자보다 더 작은 문자열이 들어오면, 인자로 준 숫자는 그냥 무시된다.






%.0d - 0일때 출력 안하기



값이 0일때는 출력을 안하고싶은 때가 있을거다.

그럴때 if문으로 분기해서 일일이 처리했다면, 이것이 반가울것이다.


바로 %와 d사이에 .을 붙임으로 해결된다.




예)

printf ("%.d", 0);


결과)

[아무것도 출력되지 않는다]






%#x - 0x를 붙여주는 hex표현



0x를 앞에다 출력하고 싶으면, %x 대신에 %#x를 쓰면 된다.

+ Recent posts