책정리/혼자 연구하는 C,C++ 1

18장 C 고급 문법

GONII 2015. 2. 22. 20:24

18.1 타입

컴퓨터의 메모리는 0과 1만을 기억할 수 있는 비트로 구성되어 있다.

이 내용은 당장 실무에 도움을 줄 수 있는 실용적인 이론은 아니지만 타입의 내부 구조를 이해함으로써 컴파일러의 동작을 좀 더 깊이 있게 이해하고 메모리에 저장된 값을 직접 평가하고 다룰 수 있는 직관력을 키울 수 있다.

18.1.1 정수의 내부

일반적으로 비트 n개가 모일 때 2n가지의 수를 표현할 수 있으며 시작 수가 0이므로 최대 표현 가능한 수는 2n-1이 된다. 부호가 있으면 표현수는 절반이 되지만 음수를 표현할 수 있다.

16비트 길이의 unsigned short형은 216-1(65535)까지 표현 가능

32비트 길이인 unsgiend int 232-1(4294967295)의 큰 값을 표현 가능하다

변수가 기억할 수 있는 최대 표현 범위를 넘어서는 경우를 오버플로우(Overflow)라고 하는데 오버플로우가 발생하면 변수는 초과된 값을 기억하지 못하고 엉뚱한 값을 가진다.

  • 예제 IncOverflow

#include <stdio.h>

   

void main()

{

unsigned short us;

   

us = 65535;

printf("us = %d\n", us );

us++;

printf("us = %d\n", us );

}

18.1.2 음수의 표현

비트의 세계에서 음수를 표현할 수 있는 방법은 여러가지가 있다

  • 일정수 감소법 : 부호 없는 값에서 일정한 수를 빼 음수를 표현한다. 일종의 평행 이동법이라고 할 수 있다. 이때 빼 주는 값을 바이어스(bias)라고 하는데 전체 범위의 절반값 정도를 빼면 음양의 범위가 비슷해질 것이다.
  • 부호 비트와 절대값 : 최상위 1비트를 부호 비트로 사용하고 나머지 2비트로 절대값을 표현하는 방법. 부호는 음, 양 두 가지 상태 중 하나이므로 1비트로 충분히 표현할 수 있으며 0을 양수 부호로, 1을 음수 부호로 약속한다. 이 방법의 단점은 부호에 따라 0이 두개가 존재하여 모호함이 발생한다는 것이다.
  • 1의 보수법 : 양수 비트를 모두 반전하여 음수를 만드는 방법이다. 음수로 만든느 연산이 간단하다는 장점이 있지만 0이 2개가 존재한다는 단점이 있다.
  • 2의 보수법 : 1의 보수에 1을 더해 음수를 표현한다. 즉 모든 비트를 반전시킨 후 1을 더하는 방법. 1의 보수법과는 달리 -0은 오버플로우되어 한가지의 0만 존재함

       

    이 외에도 비트로 음수를 표현하는 다른 방법들을 많이 생각할 수 있는데 현대의 컴퓨터들은 모두 2의 보수법으로 음수를 표현한다.

       

    컴퓨터는 -n 연산을 할 때 n을 2의 보수로 만든 후 이 값을 더하는 방식으로 뺄셈을 수행한다. 그래서 컴퓨터는 덧셈을 한느 가산기만 가지고 있으며 뺄셈을 하는 감산기는 별도로 가지지 않는다.

    보수(Complement)란 어떤 수(기수)가 되기 위해 보충되어야 하는 수를 의미하는데 가령 기수 10에 대한 3의 보수는 7이다. 일반적으로 a+b=기수 일 때 a와 b는 기수에 대해 보수 관계에 있다고 표현한다. 2의 보수란 n비트에 대해 2n을 기수로 한 보수이다. n = 8일 때 기수는 28 = 256이 되며 결국 8비트에서 2의 보수는 256이 되기 위해 더 필요한 수로 정의할 수 있다. a의 2의 보수가 b가 있을 때 a+b = 256이며 8비트에서 256은 0과 같다. 그러므로 a + b = 0이 되고 a=-b, b=-a의 관계가 성립한다.

    18.1.3 바이트 순서

    우리가 사용하는 정수, 실수 따위의 변수들은 모두 메모리에 저장된다. 메모리의 저장 단위는 8비트로 구성된 바이트인데 비해 실제 저장해야 할 값은 32비트나 64비트로 바이트 길이보다 훨씬 더 크다. 그래서 여러 개의 연속적인 바이트에 이 값들을 나누어 저장해야 한느데 어떻게 나누는가에 따라 두 가지 방식이 있다.

  • 빅 엔디안(Big Endian : 순워드) : 이 방식은 높은 자리수를 먼저 저장한다. 0x12가 가장 앞쪽 바이트에 그리고 0x32가 그 다음 바이트에 저장되는 식이다. 모토롤라 계열의 CPU와 대부분의 RISC CPU가 이 방식을 사용한다.
  • 리틀 엔디안(Little Endian : 역워드) : 이 방식은 낮은 자리수를 먼저 저장한다. 가자 ㅇ뒤쪽 바이트인 0x78이 메모리의 가장 앞쪽 바이트에 저장되며 그 다음에 0x56이, 그리고 0x12는 가장 뒤쪽에 저장된다. 인텔 계열의 CPU와 DEC의 알파칩이 이 방식을 사용한다. 메모리의 값을 읽을 때 거꾸로 읽어야 하므로 사람이 직접 읽기엔느 다소 불편한 면이 있지만 기계가 다루기는 더 효율적이고 몇 가지 연산에서 편리한 점이 있다.

   

0x1234라는 32비트의 정수값이 메모리에 저장되어 있을 때 이 값의 하위 2바이트만 읽는다고 해보자. int형의 값을 short형 변수에 대입하거나 포인터를 통해 간접적으로 값을 읽을 때 이런 일이 일어나는데 다음 그림은 정수형 포인터 pi가 가리키는 32비트 값을 (short*)로 캐스팅해서 읽는 예이다.

리틀 엔디안은 pi가 가리키는 원래 번지에서 2바이트만 읽어들이면 된다.

빅 엔디안은 뒤쪽으로 2바이트를 먼저 옮겨야 하는 번거로움이 있다.

   

타입의 확장이 일어날 때도 마찬가지이다.

리틀 엔디안은 뒤쪽에 0x00, 0x00을 덧붙이기만 하면 간단하게 32비트로 확장된다.

빅 엔디안은 선행 제로가 들어갈 공간을 만들기 위해 앞쪽으로 0x12, 0x32를 뒤쪽의 메모리로 이동시켜야 하므로 여분의 연산이 필요하다.

값을 구성하는 각 바이트를 배열처럼 다루고자 할 때는 빅 엔디안이 더 편리하다.

  • 예제 ReadEndian

#include <stdio.h>

   

void main()

{

int i = 0x12345678, j;

char *p = (char*)&i;

   

// 빅엔디안

//for( j = 0 ; j < sizeof(int) ; j++ )

//{

//        printf("%x", p[j]);

//}

//printf("\n");

   

// 리틀 엔디안

for ( j = sizeof(int)-1 ; j >= 0 ; j-- )

{

printf("%x", p[j] );

}

printf("\n");

}

아주 특수한 상황에서는 이 사실을 알아야 되는 경우도 있는데 디버깅 중에 변수가 저장된 메모리 영역을 직접 들여다본다거나 아니면 변수의 값을 바이트 단위로 직접 조립해야 하는 경우 등이다. 이 외에도 바이트 저장 방식이 다른 이기종 컴퓨터간의 네트워크 통신을 할 때, 구체적으로 팬티엄 PC와 매킨토시가 통신할 때 엔디안을 맞추어야 한느 번거로움이 있다. 소켓은 기본적으로 빅 엔디안으로 통일되어 있으므로 인텔 계열 CPU는 보낼 때 뒤집어 보내고 받은 값도 뒤집어야 원래 값을 제대로 읽을 수 있다.

18.1.4 부동 소수점

실수는 부호, 정수부, 소수부로 구성되므로 각 요소에 적당량의 비트를 할당하는 단순한 방법을 우선 생각해 볼 수 있다.

실수 -3.14는 음수부호와 정수부 3, 소수부 .14로 구성되므로 이 셋을 각각의 비트에 저장하는 방식이다.

   

  • 32비트 실수를 표현

       

    실수를 정수부와 소수부로 분할하여 표현하는 방식은 단순하기는 하지만 전혀 치밀하지 못하고 효율적이지도 않아 질적으로 다른 방법을 필요로 하는데 그 방식이 바로 부동 소수점 방식이다.

    가수는 실수의 실제값을 표현하며 지수는 크기를 표현하여 가수의 어디쯤에 소수점이 있는지를 나타낸다. 지수의 값에 따라 소수점이 움직익 때문에 이 방식으로 실수를 표현하는 방법을 부동 소수점이라고 한다.

    부동 소수점 방식의 한가지 큰 문제점이 있는데 같은 수를 표현하는 지수와 가수의 조합이 여러 벌 나올 수 있다는 점이다.

    같은 수를 표현하는 똑같은 방법이 여럿 존재하게 되면 두 변수의 상등 비교 연산을 하기가 까다로와진다. 그래서 한 수를 표현하는 방법은 하나만 존재하도록 정규화(Normalization)를 할 필요가 있는데 가수와 정수부를 한자리로 제한하면 12.345는 1.2345*101만 가능해진다. 즉, a.bcd*10n 식으로 소수점이 항상 가수의 첫 번째와 두 번째 사이에 오도록 하는 것이다.

       

  • 부호 : 부호는 음수 또는 양수 둘 중 하나이므로 1트만 있으면 된다.
  • 지수 : 지수를 n이라고 했을 때 가수부에 2n이 곱해진다. 음수 지수도 표현해야 하므로 지수는 자체에 부호를 따로 가져야 하는데 이때는 부호 비트를 따로 쓰지 않고 127의 바이어스를 적용한다.
  • 가수 : 23개의 비트로 구성되어 있으며 각 자리수에 2의 음수 거듭승으로 가중치가 부여되어 있다. 정규화 규칙에 의해 가수는 항상 이진수 1~2사이(1.~~~)여야 하며 이 규칙을 만족하기 위해 제일 왼쪽 비트(20자리)는 항상 1이라고 가정한다.
    • 예제 PrintFloat

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

   

void printFloat(float f)

{

unsigned t;

char temp[35], bin[35];

   

// 비트를 다루기 수비도록 젖ㅇ수형 변수에 대입한다.

t = *(unsigned *)&f;

   

// 선행 제로를 포함한 32자리의 2진수 문자열을 변환

itoa( t, bin, 2 );

memset(temp, '0', 35);

strcpy(temp+32 - strlen(bin), bin ) ;

   

// 부호, 지수 다음에 공백을 하나씩 넣음

bin[0] = temp[0];

bin[1] = ' ';

strncpy( bin+2, temp+1, 8 );

bin[10] = ' ';

strcpy(bin+11, temp+9);

   

printf("실수 = %f(%s), ", f, bin ) ;

   

//지수 출력

printf("지수부 = %d\n", (t >> 23 & 0xff) - 127 );

}

   

void main()

{

printFloat(0.375f);

printFloat(3.14f);

printFloat(-0.5f);

printFloat(0.1f);

}

   

0.1을 소수점 이하 10자리까지 출력한 0.10000000015라는 값이 나오는데 거듭승으로 십진수 0.1을 정확하게 표현하지 못하므로 가장 근접한 수를 유효자리 범위에서 표현한 것이다.

  • 예제 FloatError

#include <stdio.h>

   

void main()

{

float d = 0.0f;

int i;

   

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

{

d += 0.1f;

}

   

printf("%f", d);

}

   

double형은 십진수로 15자리까지 유효하다.

  • 예제 FloatError2

#include <stdio.h>

   

void main()

{

float f1, f2, f3;

f1 = 12345.0f;

f2 = 0.0001f;

f3 = f1+f2;

printf("f1 = %f\nf2 = %f\nf3 = %f\n",f1, f2, f3);

}

18.1.5 구조체의 정렬

구조체의 멤버들은 선언된 순서대로 인접한 번지에 배치된다.

구조체의 크기는 구조체에 속한 멤버들의 총 크기와 같다.

  • 예제 StructAlign

#include <stdio.h>

   

struct tag_st1

{

char c;

double d;

};

   

tag_st1 st1 = {'a', 1.234};

   

void main()

{

printf("addr = %p, &c = %p, &d = %p, size = %d\n", &st1, &st1.c, &st1.d, sizeof(st1));

}

tag_st1 구조체는 1바이트의 문자형 멤버 하나와 8바이트 실수형 멤버 하나가 포함되어 있으며 구조체의 총 키그는 9바이트가 되어야한다. 그러나 sizeof 연산자로 st1의 구조체의 실제 크기를 조사해보면 16바이크라는 결과가 나온다.

그 이유는 물리적인 기계의 성능을 최대한 끌어올리기 위해서 컴파일러가 구조체의 메모리에 배치할 두 가지 사항을 고려하여 번지를 잡기 때문이다.

첫 번째로 구조체가 시작될 번지(base)를 고를 때 가급적이면 16바이트 경계에서 시작하도록 한다. 왜냐하면 최신 CPU들은 속도 증가를 위해 캐시를 사용하는데 캐시의 단위가 16바이트로 되어 있기 때문이다. 캐시 크기의 배수 위치에 구조체를 배치하면 이 구조체를 자주 액세스할 때 캐시 용량을 덜 차지하면서 빠르게 액세스 할 수 있다. 만약 16바이트 경계의 양쪽에 걸치게 되면 캐시도 많이 차지할 뿐더러 속도도 느려진다.

두 번째로 구조체의 멤버를 배치할 때 멤버의 오프셋도 액세스하기 유리한 위치로 조정한다. 별다른 지정이 없으면 멤버의 크기에 따라 자연스러운 경계 위치에 맞추도록 되어 있는데 예를 들어 int는 4바이트, double은 8바이트 경계에 맞춘다.

위 예제의 경우 c가 1바이트를 차지하고 난 후 d는 다음 8바이트 경계에 배치되므로 c와 d 사이에 7바이트는 버려지고 사용되지 않는다. 이렇게 사용되지 않고 버려지는 공간을 패딩(Padding)이라고 한다.

컴파일러는 CPU가 메모리를 최대한 빠른 속도로 액세스할 수 있도록 구조체의 베이스와 멤버의 오프셋을 조정해서 배치하는데 이를 구조체의 정렬(alignment)라고 한다.

   

만약 특정 멤버가 배치된 오프셋을 조사하고 싶다면 stddef.h에 다음과 같이 정의되어 있는 offset 매크로 함수를 사용한다.

#define offsetof(s,m) (size_t)&(((s*)0)->m)

  • 예제 offsetof

#include <stdio.h>

#include <stddef.h>

   

void main()

{

struct node

{

int a;

double b;

char c[16];

node *prev;

node *next;

};

node a, b;

   

printf("a의 오프셋 = %d\n", offsetof(node, a));

printf("b의 오프셋 = %d\n", offsetof(node, b));

printf("c의 오프셋 = %d\n", offsetof(node, c));

printf("prev의 오프셋 = %d\n", offsetof(node, prev));

printf("next의 오프셋 = %d\n", offsetof(node, next));

}

   

구조체의 크기가 필요하다면 반드시 sizeof연산자를 사용해야 하며 구조체가 간단하다고 상수를 사용하는것은 위험하다.

18.2 전처리기

18.2.1 #과 ##

#과 ##은 전처리기의 연산자로 컴파일러가 #define 전처리 과정에서만 사용하는 특수한 연산자이다. C언어 자체의 연산자는 아니므로 우선순위나 결합 규칙 등은 적용되지 않는다.

#연산자(stringizing operator)는 #define문의 인수 앞에 사용되며 피 연산자를 문자열로 바꾸는 역할을 한다.

  • 예제 SharpOp

#include <stdio.h>

   

#define result(exp) printf(#exp"=%d\n", exp);

   

void main()

{

result(5*3);

result(2*(3+1));

}

result(5*3) 호출문은 전처리기에 의해 다음과 같이 치환된다.

result(5*3)

printf(#5*3"=%d",(5*3)); 인수 치환

printf("5*3""=%d",(5*3)) #다음의 인수가 문자열이 됨

printf("5*3=%d",(5*3)); 인접한 문자열을 합침

   

실인수 내의 공백은 하나만 인정되며 둘 이상의 공백은 하나만 남기고 삭제된다. 실인수 내에 주석이 있으면 이 주석은 하나의 공백으로 대체된다.

   

  • 예제 BinaryConst

#include <stdio.h>

#include <stdlib.h>

#define BIN(a) strtol(#a, NULL, 2)

   

void main()

{

printf("%x\n", BIN(000010010001101001111000001011100));

}

  • 예제 SharpSharpOp

#include <stdio.h>

   

#define var(a,b) (a##b)

   

void main()

{

int var(Total, Score);

TotalScore = 256;

printf("총점 = %d\n", TotalScore);

}

  • 예제 DefineType

#include <stdio.h>

   

#define defptype(type) typedef type *p##type

   

void main()

{

defptype(int);

defptype(double);

defptype(char);

   

pint pi;

   

int i = 3;

pi = &i;

printf("i = %d\n", *pi);

}

18.2.2 조건부 컴파일

조건부 컴파일 지시자(Conditional Compile Directive)는 지정한 조건의 진위 여부에 따라 코드의 일정 부분을 컴파일할 것인지 아닌지를 지정한다. 컴파일되기 전에 조건을 평가하며 코드를 컴파일 대상에 포함시키거나 제외시키는 역할을 한다.

조건부 컴파일 지시자를 잘 활용하면 한벌의 코드를 조건에 따라 다르게 컴파일하여 상이한 실행 파일을 만들 수 있다.

조건부 컴파일 기능이 없다면 실행 파일별로 소스를 따로 유지해야 하므로 무척 번거로와진다. 조건부 컴파일 지시자는 다양한 상황과 목적에 맞게 소스를 컴파일 하여 호환성과 이식성을 확보하는 수단으로 빈번하게 활용된다.

#ifdef 매크로명

코드

#endif

   

두 버전 중 한 가지 버전에만 기능을 추가해야 한다면 조건부 컴파일 블록을 배치한다.

#ifdef PROFESSIONAL

고급 기능

#endif

#ifndef는 #ifdef와 반대의 조건을 점검하는 지시자이다. #ifdef는 매크로가 정의되어 있을 때만 컴파일하지만 #ifndef는 반대로 매크로가 정의되어 있지 않을 때만 컴파일한다.

   

#ifdef PROFESSIONAL

전문가용 코드

#else

일반용 코드

#endif

   

디버깅 중 사용하는 조건부 컴파일

#ifdef _DEBUG

printf("변수 값 확인, i = %d\n", i );

#endif

이 코드는 개발 중에 디버깅 편의를 위해 삽입된 임시 코드이며 실제로 릴리즈 할 때는 컴파일 하지 말아야 한다.이러한 지시자가 없다면 코드를 일일이 넣었다 뺐다 해야 하므로 무척 불편할 것이다.

18.2.3 #if

#ifdef, #ifndef는 매크로의 존배 여부만으로 컴파일 조건을 판단하며 매크로가 어떤 값으로 정의되어 있는지는 평가하지 않는다. 이에 비해 #if는 매크로의 값을 평가하거나 여러 가지 조건을 결합하여 컴파일 여부를 결정하는 좀 더 복잡한 전처리문이다.

#if 조건1

코드1 // 조건1을 만족하면 코드1을 컴파일

#elif 조건2

코드2 // 조건2가 만족되면 코드2를 컴파일

#else

코드3 // 둘 다 맞지 않으며 코드3을 컴파일

#endif

   

  • #if 전처리문의 작성 규칙
    • 매크로 값을 비교할 때는 상등, 비교 연산자를 사용한다

      #if( LEVEL == 3 )

  1. 비교 대상은 정수 상수여야 하며 실수나 문자열은 매크로와 비교할 수 없다

    #if( VER == 3.14 ) // 에러

    #if( NAME == "kim" ) // 에러

    #if( LEVEL == BAIC ) // 가능

  2. 수식 내에서 간단한 사칙 연산을 할 수 있다.

    #if( LEVEL*2 == 6 )

  3. 논이 연산자로 두 개 이상의 조건을 동시에 평가할 수 있다.

    #if( LEVEL == 8 && VER != 3 )

  4. defined 연산자로 매크로의 존재 여부를 평가할 수 있다.

    #if( LEVEL == 8 || defined(PROFESSIONAL )

  5. #if 다음의 조건부 컴파일 블록에는 어떤 문자이든 올 수 있다.

   

18.2.4 #undef

#undef 는 #define의 반대되는 동작을 하는 전처리문이다. #define 매크로를 정의하는데 비해 #undef는 정의되어 있는 매크로를 삭제한다.

  • 예제 undef

#include <stdio.h>

   

void main()

{

#define SIZE 10

printf("SIZE = %d\n", SIZE);

#undef SIZE

#define SIZE 20

printf("SIZE = %d\n", SIZE);

}

18.2.5 미리 정의된 매크로

미리 정의된 매크로(Predefined Macro)는 컴파일러가 제공하는 매크로이다.

주로 컴파일러가 현재 상황이나 컴파일 중에 참고할만한 정보를 알려주기 위한 용도로 사용한다. #define으로 정의하지 않아도 사용할 수 있으며 재정의할 수도 없다.

매크로명

설명

__DATE__

컴파일될 때의 날짜를 나타내는 문자열

__TIME__

현재 소스가 최후 컴파일 된 시간을 나타내는 문자열

__TIMESTAMP__

현재 소스가 최후로 수정된 날짜와 시간을 나타낸다

__FILE__

현재 소스 파일의 완전 경로

__LINE__

이 매크로가 포함된 소스상의 줄 번호, 10진 정수

__STDC__

컴파일러가 ANSI C 표준을 따를 경우 1로 정의되며 그렇지 않을 경우 정의되지 않는다. C++로 컴파일 할 때는 이 매크로가 없다.

  • 예제 PreDefMacro

#include <stdio.h>

   

void main()

{

printf("오늘은 %s이고 최후 컴파일된 시간은 %s\n", __DATE__, __TIME__);

printf("이 파일이 최종 수정된 시간은 %s\n", __TIMESTAMP__);

printf("이 파일은 %s이고 이 줄은 %d\n", __FILE__, __LINE__);

#ifdef __STDC__

printf("이 컴파일러는 ANSI C표준\n");

#else

printf("이 컴파일러는 ANSI C표준의 확장(C++)\n");

#endif

}

   

비주얼 C++ 컴파일러가 제공하는 미리 정의된 매크로

매크로명

설명

_DEBUG

디버그 모드로 컴파일 중일 때만 정의

__cplusplus

C++ 모드로 컴파일 중일 때만 정의

_DLL

DLL 프로젝트일 때만 정의

_MSC_VER

비주얼 C++ 컴파일러 버전을 나타냄 6.0은 1200, 7.0은 1300

_WIN64

Win64환경일 때 정의

__COUNTER__

참조될 때마다 1씩 증가하는 정수값, 유일한 이름을 만들고자 할 때 이 매크로를 사용함. 7.0에서만 제공

_M_ALPHA, _M_IX86,

M_IA64, _M_MPPC

CPU의 종류이다. 플랫폼에 따라 약간씩 코드가 달라져야 할 때 이 매크로를 차조

18.2.6 #error, #line

#error 전처리문은 지정한 에러 메시지를 출력하고 전처리 과정에서 컴파일을 중지하도록 한다.

#line 전처리문은 __LINE__, __FILE__ 매크로를 재정의한다.

18.3 pragma 지시자

C언어의 장점 중 하나는 어느 운영체제나 플랫폼으로 쉽게 이식될 수 있는 이식성(Portability)이다. 그러나 이 이식성은 어디까지나 소스차원에서 이식 가능성을 의미하는 것이지 컴파일된 결과인 실행 파일은 그렇지 않다. C언어를 특정 플랫폼에 맞게 컴파일하여 고유의 실행 파일을 만들어 내는 컴파일러는 본질적으로 플랫폼에 종속적이다.

그래서 각 플랫폼에서 실행되는 컴파일러는 플랫폼의 고유한 기능을 수행하기 위한 지원을 해야 한다. #pragma 지시자는 플랫폼별로 다른 이런 기능에 대한 지시 사항을 컴파일러에게 전달하는 방법이다. #문자로 시작하므로 전처리 명령처럼 보이지만 컴파일 지시자이다.

   

#pragma 토큰문자열

#pragma 다음에 지시 사항을 전달하는 토큰 문자열이 오는데 이 토큰의 종류는 컴파일러별로 다르다.

비주얼 C++의 pragma 토큰 문자열

alloc_text, auto_inline, bss_seg, check_stack, code_seg, comment, component, conform, const_seg, data_seg, deprecated, function, hdrstop, include_alias, int_seg, inline_depth, inline_recursion, intrinsic, managed, message, once, optimize, pack, pointers_to_members, pop_macro, push_macro, runtime_checks, section, setlocale, unmanaged, vtordisp, warning

18.3.1 once

once 지시자를 헤더 파일 선두에 써 두면 컴파일러는 딱 한 번만 헤더파일을 포함하여 컴파일 시간을 절약한다.

다음 조건부 컴파일 지시자로 한 번만 포함되도록 하는 효과가 동일하다

#ifndef _SOME_HEADER_FILE

#deinfe _SOME_HEADER_FILE

// 헤더파일 내용

#endif

#pragma once

18.3.2 pack

pack 지시자는 이후부터 선언되는 구조체의 정렬 방식을 지정한다. 프로젝트 설정 대화상자에서 구조체 정렬 방식을 각 모듈별로 젖ㅇ할 수 있지만 pack 지시자는 소스의 중간에서 원하는 구조체에 대해 정렬 방식을 변경할 수 있도록 한다는 점이 다르다.

#pragma pack(2)

struct st1 { short s; int i; } ;

#pragma pack(4)

struct st2 { shorts; int i; } ;

정렬값의 디폴트는 8이며 n을 생략하여 pack()이라고만 적으면 디폴트 정렬값으로 돌아간다.

   

pack(push, n)명령은 현재의 정렬 상태를 스택에 저장하면서 정렬 값을 n으로 변경하는데 n을 생략하면 현재 정렬값을 스택에 저장하기만 한다. pack(pop, n)은 스택의 최상단에 있는 정렬값을 제거하고 새로운 정렬값을 n으로 변경하는데 n을 생략하면 스택에서 꺼낸 정렬값을 새로운 저렬값으로 설정한다.

#pragma pack(2)

struct st1 { short s; int i; }; // 2바이트 정렬

#pragma pack(push, 4) // 푸쉬하면서 4바이트 정렬로 바꿈

struct st2 { short s; int i; }; // 4바이트 정렬

#pragma pack(pop) // 원래 값 복원

struct st3 { short s; int i; }; // 2바이트 정렬

18.3.3 warning

컴파일러는 컴파일한 결과를 에러와 경고라는 진단 메시지로 출력한다. 이 중 에러는 명백하게 틀린것이므로 반드시 수정한 후 컴파일해야 하나 경고는 경우에 따라 참고만 하고 무시해도 상관없다.

  • 예제 Warning

#include <stdio.h>

   

void main()

{

int i, j, k;

unsigned u,v = 1234;

double d = 3.14;

   

i = u;

if( i = 3 )

{

i = d;

}

if ( i == v )

{

switch(i)

{

}

}

}

  • C4700 : 지역변수를 초기화하지 않고 사용함
  • C4244 : i = d 대입문에 의해 i에 3이 대입되는데 이 과정에서 하강 변환 발생 소수점 이하 0.14가 버려짐
  • C4018 : 부호 있는 변수와 부호 없는 변수를 상등 연산했으므로 좌우의 타입이 맞지 않음
  • C4060 : switch문의 case가 전혀 없이 switch문자체가 있으나 마나한 문장이라는 뜻
  • C4706 : 조건문에 대입 연산자를 사용했다는 경고

   

컴파일러가 경고를 출력하는 방법을 바꾸고 싶다면 다음 명령을 사용

#pragma warning(경고제어문:경고번호)

제어문

설명

once:번호

반복되는 경고를 한번만 출력

defulat:번호

원래 설정대로 되돌림

disable:번호

경고를 출력하지 않음

error:번호

경고를 에러로 처리

레벨:번호

경고의 레벨(1~4)을 변경

push[,n]

모든 경고의 레벨을 저장, n이 있을 경우 저장과 동시에 전역 경고 레벨을 n으로 변경

pop

스택에 마지막ㅁ으로 저장된 경고 레벨을 복원

 

반응형

'책정리 > 혼자 연구하는 C,C++ 1' 카테고리의 다른 글

20장 알고리즘  (0) 2015.02.26
19장 자료구조  (0) 2015.02.25
17장 파일 입출력  (0) 2015.02.22
16장 함수 고급  (0) 2015.02.20
15장 포인터 고급  (0) 2015.02.19