728x90

여기서의 차이는 ++i가 증감한 뒤 i를 리턴하고 i++가 리턴하고 증감하는 차이를 설명하고자 하는게 아니다. 

여기서는 보다 자세하게 실제 내부적으로 어떠하게 구현되어 있는지 알아볼 필요가 있다.

우선  ++i의 내부를 살펴보면

 

i = i + 1;

return i;

 

인데 반해 i++은

 

variouble temp;

temp = temp + 1;

return temp

 

로써 temp라는 변수를 선언한 후 연산한다. 즉 메모리의 temp라는 값이 남으며 temp를 선언해야 하기 때문에 한번 더 연산을 해야하는 과정이 생겨있다.

즉 iterator를 사용하거나 반복문에서 사용할 경우에는 i++보다는 ++i가 효율적이다.

 

출처 :  http://blog.naver.com/sorkelf/40146367692

728x90
728x90

#pragma 

Precomplier 즉 전처리기로서 컴파일 하기 이전에 해당 명령을 처리해주는 명령어이다.

Visual C++에는 다양한 프로젝트 옵션 등이 제공되지만

혹여 다른 컴파일러를 사용할 경우도 생길지 모르니

옵션 위주보다는 #pragma를 사용하는 것을 권장한다.

몇가지 옵션을 소개한다.

 

#pragma once : 해당 소스가 단 한번만 포함되게 한다.

 - 여러번 include 되는 것을 방지하며 이 파일은 한번만 포함되는 것을 알 수 있다.

   #ifndef #endif 와 같은 방식이다.

 

 

#pragma pack : 구조체의 정렬 방식을 지정한다.

 - 제어문 종류 :: pack(n) : n으로 정렬 방식을 바꾼다.

     default : Default 정렬 방식을 바꾼다.

     pack(push, n) : n으로 정렬 방식을 바꾸고 기존 정렬 방식은 스택에 저장한다.

     pop : 스택에 저장한 정렬방식으로 다시 돌린다.

     여기서 n은 컴파일러가 저장된 메모리에 데이터를 정렬하는 방법을 결정하는 바이트 크기이다.

     보통은 4바이트이다.

 

ex) #pragma pack(1)

typedef struct

{

     char ch1;      // 1바이트

     char ch2;      // 1바이트

     char* pchar; // 4바이트

     int a;            // 4바이트

     int* pa;         // 4바이트

}STUDENT, *LP_STUDENT;

#pragma pack() 이 경우 구조체의 크기는 14바이트여야 하나 실제로 sizeof()를 구해보면 16바이트이다. 이 때 pack을 사용하면 빈공간없이 14바이트로 구조체를 사용할 수 있다.

 

 

#pragma data_seg : dll에서 데이터를 공유하고자 할 때 사용된다.

 

 

#pragma warning : 컴파일시 나오는 경고를 설정한다.

 - 제어문 종류 : once : 번호 -> 반복되는 경고를 한번만 출력

      default : 번호 -> 원래 설정대로 되돌린다.

      disable : 번호 -> 경고 출력을 하지 않는다.

      error : 번호 -> 경고를 에러로 처리한다.

      level : 번호 -> 경고의 레벨을 변경한다.

      push[, n] -> 모든 경고의 레벨을 저장한다. n이 있을 경우 저장과 동시에 전역 경고 레벨을 n으로 변경한다.

      pop -> 스택에 마지막으로 저장된 경고 레벨을 복원한다.

 

ex) #pragma warning(disable : 4244)

 - 4244번의 Warning에 대한 경고를 표시하지 않는다.

    #pragma warning(3 : 4244)

 - 4244번 경고를 레벨 3으로 올린다.

 

 

#pragma message("Hello") : 컴파일 시 특정 문장을 표시하게 한다.

ex)

      #define __DISMSG__(x) #x

      #define __PREFIX__(x) __DISMSG__(x)

      #define __WARNING__ __FILE__ "("__PREFIX__(__LINE__)") warning : "

      #define __ERROR__  __FILE__ "("__PREFIX__(__LINE__)") error : "

 

      #pragma message(">> compliing begin point...")

 

      int main()

      {

           char szMsg[100];

           memset(szMsg, 0x00, sizeof(szMsg));

           strcpy(szMsg, "helloworld");

           #pragma message(">> "__WARNING__ "strcpy로 하면 안전하지 않다.")

           printf("%s\n", szMsg);

      }

 

      #pragma message(">> compliing end point...")

 


 

 

#pragma intrinsic(함수명1, 함수명2) : 해당 함수를 호출시 function call이 아닌 inline 형식으로 코드자체를 함수 사용하는 장소에 

                                           삽입시켜 최적화를 시켜준다.

 

 

#pragma function(함수명1, 함수명2) : 해당 함수를 function call 형태로 구현되도록 지시한다.

                                           둘중에 하나를 선언 후 다시 변경할 때까지 pragma가 기술된 첫 함수부터 영향을 미쳐 소스

                                           코드의 마지막 라인까지 영향을미친다.

 

 

#pragma comment(lib, "링크명.lib") : 해당 dll이나 라이브러리를 링커에 포함시키도록 한다.

 

 

#pragma optimize("옵션", on/off) : 함수 밖에 선언이 되며 pragma가 선언된 후 정의된 첫 번째 함수

                                                 에 영향을 미치도록 한다.

옵션 - g : 전역 최적화 옵션을 활성화

     - p : Floating Point의 Consistency를 향상시킨다.

     - s or t :

     - y : 프로그램 스택의 프레임 포인터를 일반화 시킨다.

     - 옵션의 아무것도 주지 않고 뒤에 파라미터를 사용시 모든 옵션에 대해 설정이 적용된다.

 

ex) #pragma optimize("", on)

    #pragma optimize("tp", off)

 

 

#pragma region & endregion : 멤버 함수나 변수들을 일정한 분류를 시켜준다. 해당 영역을 줄이거나

                                          확장시켜 코드의 가독성을 높일 수 있다.

 

ex) #pragma region 테스트용 함수들

void Test1() {}

void Test2() {}

void Test3() {}

  #pragma endregion 테스트용 함수들

 

더 자세한건 msdn을 찾아보자.

http://msdn.microsoft.com/ko-kr/library/h7k3a0bw.aspx

 

출처 : http://blog.naver.com/sorkelf/40146367692

728x90
728x90

멤버 이니셜라이져(Member Initializer) 

 

말 그대로 클래스의 멤버를 초기화 하는 것으로

 

클래스

{

     멤버변수 1, 멤버변수 2;

}

클래스명(타입1 변수1, 타입2 변수2) : 멤버변수1(변수1), 멤버변수2(변수2){} 방식이다.

 

ex)

class Student

{

     int id;

     float num;

}

 

Student(int _id = 0, float ft_num = 0.0f) : id(_id), num(ft_num){}

 

이 문장들은 멤버변수 id와 num을 변수 _id 와 ft_num으로 초기화 하라는 말이다.

멤버 이니셜라이져는 생성자의 몸체부분보다 먼저 실행되는 특징이 있기 때문에 상속을 받은 클래스에서 부모 클래스의 변수를 초기화 해야 할 필요가 있을 경우 사용한다.

또한 const 멤버 변수를 초기화 할 수 있는 특징도 가지고 있기에 유용하게 쓰이는 문법이다.

그래서 const 멤버 변수는 반드시 이니셜 라이져를 이용해서 초기화 한다.

 

effective c++에서는 클래스의 변수를 초기화할 때에는 생성자의 함수 몸체에서 하지말고 멤버 이니셜라이져를 사용해라고 한다. 이유는 생성자의 멤버 이니셜라이져는 초기화이지만, 생성자의 함수 몸체에서 한다면 그것은 그냥 대입이라고 한다.

 

 

출처 : http://blog.naver.com/sorkelf/40146367692

728x90
728x90

Zeromemory(&구조체, sizeof(구조체)); 

memset(&구조체, 0, sizeof(구조체));

 

이 두개는 컴파일 최적화 옵션을 해제하고 디스 어셈블 해보면

004017F0     push     24h

004017F2     push     0

004017F4     lea        eax, [ebp-70h]

004017F7     push     eax

004017F8     call       memset(401000h)

004017FD    add       esp, 0CH

 

결과 값과 컴파일 결과가 일치하는 것을 알 수 있다.

인자를 스택에 넣고, memset의 주소를 콜하는 방식이다.

실제적으로 두개의 차이는 없다고 할 수 있지만 Zeromemory는 0으로만 채워줄 뿐 memset과 같이 원하는 값을 초기화 값으로 줄 수 없다.

단지 Zeromemory는 0x00으로 해주는 수고를 줄여줄 뿐이다.

가독성면으로 보더라도 Zeromemory가 0으로 초기화 하겠다는 것을 쉽게 알 수 있으므로 별 차이가 없다면 Zeromemory를 쓰도록 하자.

 

또 다른 구조체 초기화 방법은

구조체타입 객체변수 = {0};

ex) Student S1 = {0};

 

코드를 디스 어셈블 해보면 결과는

00401800     mov     dword ptr[ebp-4Ch], 0

00401807     xor      ecx, ecx

00401809     mov     dword ptr[ebp-48h], ecx

0040180C    mov     dword ptr[ebp-44h], ecx

0040180F    mov     dword ptr[ebp-40h], ecx

 

2번째 줄에서 볼 수 있듯이 xor, ecx, ecx 명령어로 단순히 그 값을 0으로 채워주는 것 밖에 없다.

한가지 알아 둘 것은 구조체의 크기가 커지면 컴파일 시 자동으로 memset으로 변환한다는 것이다. 

 

즉  3개의 차이는 그다지 크지 않으므로 자신이 보기 편하고 가독성이 좋다고 생각하는 것을 쓰도록 하자.

 

출처 : http://blog.naver.com/sorkelf/40146367692

728x90
728x90

volatile 키워드 

 

컴파일러의 최적화 옵션의 적용을 받지 않도록 하는 선언자.

비슷한 것으로 constant 선언이 있는데 서로 다른 점이라면  constant 선언의 경우에는 변수 정의 시점에 대입된 값이 어떠한 경우에도 변경되지 않도록 보호하는 형태 즉 상수의 형태로 보존되도록 하라는 옵션이다.

 

이와 달리 volatile의 경우에는 컴파일러가 프로그램의 코딩을 최적화를 통해 임의로 수정하지 못하도록 하는 명령어로 변수 선언시 임의로 레지스터에 변수를 올리지 않게 하고 메모리에 올리게 하며 코드는 임의로 수정을 하지 못하게 한다.

 

즉, 해당 변수의 데이터가 변할 수 있기 때문에 항상 해당 메모리에 접근해서 데이터를 직접 참조하라는 지시자이다.

 

간단한 예)

int i, a, b;

 

a = 0; b = 0;

 

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

     b += a * 100;      

 

이런 코드를 작성한 경우.. 일반적인 컴파일러로 최적화를 하게 되면

 

int i, a, b;

 

a = 0; b = 0;

 

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

     b += 0;       // 연산을 하지 않게 컴파일러가 알아서 보정해 버린다.

                         (최적화 옵션에 따라 다양하게 변할 수 있다.)

 

이처럼 컴파일러 차원에서 옵티마이저시 위와 비슷한 로직으로 자동 변환이 되게 되는데(정확히는 어셈블 과정에서 코드가 변환되어 비 효율적이라 판단되는 부분을 수정 또는 제거해 버린다.)

프로그램에 따라서는 이렇게 코드가 변경되면 안되는 경우가 있다.

즉 컴파일을 할 때 최적화 옵션으로 인해 변경될 경우를 방지하기 위한, 반드시 내가 명시한대로 실행할 필요가 있을 경우 사용하는 키워드이다.

 

또한 변수에 값을 대입 할 때 (메모리에 write) 항상 공유 메모리에 직접 쓰거나 읽지 않고 쓰기 버퍼(저장 버퍼)에 값을 임시로 저장했다가 실제 공유메모리에 write할 수 있다. 이럴 경우 멀티 스레드 환경에서 메모리를 읽었을 때 기대하는 값이 나오지 않을 수 있다.

 

 

register 키워드

 

해당 변수를 메모리에 저장하는 것이 아닌 실제로 연산이 일어나는 레지스터에 저장되게 한다.

레지스터는 고속 연산이 가능하므로 상당한 실행 시간을 보장 받을 수 있지만

용량의 한계가 있으므로 간단한 프로그램 위주로 많이 쓰인다.

 

출처 : http://blog.naver.com/sorkelf/40146367692

728x90
728x90

1. static_cast 

묵시적 캐스르로 변수형을 변환해준다.

 

간단한 예)

int d = 65;

char ch = d;                              // 컴파일러가 묵시적으로 캐스팅을 해줌

char ch = static_cast<char>(d);  // 명시적으로 캐스팅을 해줌

  

묵시적 캐스팅과의 차이점

 - 클래스 포인터에 대해 묵시적 캐스트는 is-a관계가 성립하는 경우만 허용하고 static_cast는 is-a관

   계 뿐만 아니라 상속 관계일때도 캐스팅을 해준다.

 

Truck* pTruck = new Car;

Truck* pTruck = static_cast<Truck*>(new Car);

명시적 변환이기는 하나 컴파일 타임에 타입 체크를 하는 등 대체적으로 안전한 형 변환이다.

 

 

2. reinterpret_cast

일반적으로 허용하지 않는 위험한 형변환을 할 때 사용한다.

단지 안에 있는 비트열만 보고 원하는 형으로 강제 변환을 할 때 사용한다.

포인터를 정수로 변환하는 작업을 사용할 때 많이 사용한다.

 

간단한 예)

int a, b;

a = reinterpret_cast<int>(&b);

 

간단한 예2)

class A

{

private :

     char a;

     char b;

}

위 클래스 변수 b에 억지로 값을 기록하고자 한다면 다음과 같이 사용하면 된다.

A a;

char* p = reinterpret_cast<char*>(&a);

*(p+1) = 5;

 

 

3. dynamic_cast

유일하게 c스타일의 형변환으로 불가능한 캐스팅.

상속관계에 있는 클래스의 형변환을 수행하며, 동시에 안전한지 검사 한다.

 

간단한 예)

Truck* pTruck = new Car;

Truck* pTruck = dynamic_cast<Truck*>(new Car);

 

 

4. const_cast

const 속성이나 volatile속성을 해제 할 때 사용한다.

변수를 정의할 때 volatile 키워드를 사용한다.

즉 상수로 정의한 변수를 변경하고 싶을 때 사용한다.

 

간단한 예)

RECT rectA;

CONST RECT rectB;

 

rectA = const_cast<RECT>(rectB);

 

출처 : http://blog.naver.com/sorkelf/40146367692

728x90
728x90

업무용 컴퓨터에 얼마 전부터 (글 작성시점은 2016년 3월) 


"컴퓨터에 api-ms-win-crt-runtime-l1-1-0.dll이(가) 없어 프로그램을 시작할 수 없습니다. 프로그램을 다시 설치하여 이 문제를 해결하십시오."


api-ms-win-crt-runtime-l1-1-0.dll is missing <-- 구글링한 문장은 이것.


란 메시지 박스가, 일부 프로그램을 실행할 때마다 떴다. 메시지 박스의 "확인"버튼만 눌러주면 프로그램은 정상적으로 실행되는 것 같았지만, 꺼림직하여 구글링으로 문제의 해결방법을 찾았다.


문제는 visual studio 2015용 visual c++ 재배포 패키지를 마이크로소프트에서 다운받아 설치하여 해결됐다. 32비트 재배포 패키지와 64비트 재배포 패키지를 받을 수 있는데, 나의 경우는 64비트 재배포 패키지만 받아서 깔아 해결됐다.


문제가 발생한 환경은 한국어 윈도우7 64비트.


출처 : http://daewonyoon.tistory.com/179



728x90
728x90

템플릿은 C++을 강력한 언어로 만들어주는 기법 중의 하나입니다.

 

템플릿이라는 문법을 통해서 편리하게 STL을 사용할 수도 있습니다.

 

이 중에 auto_ptr이라는 스마트 포인터가 있습니다. 

 

auto_ptr은 <memory>에 구현되어 있습니다. 

 

제목에서 보이듯이 이 스마트 포인터는 반만 스마트합니다.

 

그 이유를 차근차근 보도록 하겠습니다.

 

auto_ptr은 템플릿 클래스로 만들어져 있기 때문에,

어떤 타입의 포인터든 받을 수 있습니다.

 

클래스의 특징 중 하나는 Scope(유효 범위)를 벗어나면 소멸자가 호출된다는 것인데

 

이 특징을 이용하고 있습니다.

 

만약 다음과 같은 코드가 있다면 메모리의 누수가 생깁니다.

 

void main()

{

        int* pInt = new int;

        *pInt = 10;

        cout<<*pInt<<endl;

        // delete pInt;

}

 

new로 동적할당을 하고 delete를 호출하지 않아서 메모리 누수가 있습니다.

 

void main()

{

        auto_ptr<int> pInt(new int);

        *pInt = 10;

        cout<<*pInt<<endl;

}

 

이렇게 auto_ptr을 사용 가능합니다.

 

<> 안에 사용할 타입을 넣어주시면 됩니다.

 

auto_ptr은 구현을 보시면 소멸자(~auto_ptr())에 다음과 같이 되어 있습니다.

 

~auto_ptr()

{

        delete _Myptr;
}

 

_Myptr은 auto_ptr이 내부적으로 관리하는 포인터입니다.

 

생성자가 동적할당된 주소를 받게 되어 있는데, 그 주소를 가리키는 포인터입니다.

 

Visual Studio에서는 _Ty *_Myptr; 으로 되어 있습니다.

 

템플릿에서 흔히 쓰는 표현으로는 T_Myptr이 됩니다.

 

넘겨받은 주소를 저장하고 갖고 있다가 소멸자가 호출될 때,

 

delete _Myptr; 을 호출하는 것 뿐입니다.

 

여기서 auto_ptr의 한 가지 단점이 나옵니다.

 

동적할당을 배열 단위로 하면 정상적으로 메모리 해제가 안된다.

 

new int [5];로 할당하면 delete[] _Myptr로 바뀌어야 하는데

 

이미 소멸자의 코드는 delete _Myptr;로 고정되어 있습니다.

 

이 경우에는 메모리의 누수를 가져올 수 있습니다.

 

그리고 malloc()으로 할당된 메모리에 대해서도 정상적으로 해제가 안될 수 있습니다.

 

같은 이유로 malloc()은 free()와 쌍을 이뤄야 하기 때문입니다. 

 

이러한 문제를 다른 스마트 포인터는 functor(함수 객체)라는 

 

문법을 사용하여, 회피하고 있습니다. 

 

소멸자에서 호출할 메모리 해제 코드를 직접 지정할 수 있습니다. 

 

그리고 대입 등으로 내부의 포인터를 복사하면 복사가 되는 것이 아니라

 

이전의 auto_ptr은 NULL로 무효화처리 됩니다.

 

a = b;

 

위와 같을 때 일반적으로는 a와 b값이 같아지지만,

 

auto_ptr은 a에 b의 값이 들어가면서 b는 NULL로 바뀌어버립니다.

 

이렇게 되는 이유는 만약 a와 b가 같은 위치를 가리키고 있을 때, a가 먼저 delete로

 

해당 메모리 영역을 해제해버리면 b는 이미 해제된 영역을 다시 delete하기 때문에

 

문제가 생기기 때문입니다.

 

여기서 auto_ptr의 두 번째 단점이 나옵니다.

 

한 곳의 위치를 가리키는 2개의 auto_ptr을 생성할 수 없다.

 

억지로 가리킬 수는 있어도 나중에 해제 시에 반드시 문제가 생기게 됩니다.

 

auto_ptr에 release()라는 메소드가 존재하는데

 

자기가 갖고 있는 포인터를 반납(리턴)하면서 포인터를 NULL로 바꿉니다.

 

그래서 release()를 사용하면, 포인터를 돌려받고, auto_ptr 객체가 무효화 됩니다.

 

그래서 스마트 포인터들은 이 문제를 참조 카운트(Reference Count)를 통해서

 

해결합니다.

 

참조 카운트를 유지하면서 대입을 하거나 했을 때, 카운트를 하나씩 올려주고

 

소멸자가 호출되면, 카운트를 다시 하나씩 내려줍니다.

 

그러다가 마지막에 카운트가 0이 되는 순간 메모리를 해제하는 방법입니다.

 

shared_ptr이 이런 스마트 포인터 중 하나이며  

 

COM에도 이런 기능을 하는 스마트 포인터들이 존재합니다.(CComPtr 등등...) 

또, C++11에서는 auto_ptr에 Deprecated 되었다고 나옵니다.

 

앞으로를 위해서는 auto_ptr은 절대 사용하면 안됩니다.

 

unique_ptr은 auto_ptr을 대체합니다.  

그 외에도 대체할 수 있는 스마트 포인터가 많이 있으므로 다른 스마트 포인터를

 

사용하시면 됩니다.


출처 : http://psychoria.blog.me/40155382971

728x90

+ Recent posts