서론 :

지금 까지 나는 byte padding이 cpu 레지스터 사이즈를 따라 일괄적으로 적용 되는 줄 알고 있었다(http://nsjokt.springnote.com/pages/4716509). 하지만 오늘 exe와 dll 사이에서 일어난 문제를 해결하며 찾아본 자료에서 byte padding은 아래의 네 가지 규칙에 따라 구조체에 따라 다르게 적용 된다는 사실을 알았다. 예를 들어 설명 하자면 지금까지 페이지 사이즈가 4byte인 어플리케이션에서는 byte padding 사이즈를 따로 지정해 주지 않는 한 1 byte 짜리 멤버 변수를 가진 구조체나 4 byte 멤버 변수를 가진 구조체의 sizeof 결과가 모두 4 byte로 같을 것이라고 생각 했으나 실제로는 각각 1 byte, 4 byte로 다르게 align 되고 있었다.

 

위에서 언급한 네 가지 규칙을 살펴 보자면(일단 VC 기준, gcc는 다른 옵션이 있겠지) :

  1. Unless overridden with __declspec(align(#)), the alignment of a scalar structure member is the minimum of its size and the current packing.
  2. Unless overridden with __declspec(align(#)), the alignment of a structure is the maximum of the individual alignments of its member(s).
  3. A structure member is placed at an offset from the beginning of its parent structure which is the smallest multiple of its alignment greater than or equal to the offset of the end of the previous member.
  4. The size of a structure is the smallest multiple of its alignment larger greater than or equal the offset of the end of its last member.

출처 : http://msdn.microsoft.com/ko-kr/library/83ythb65(v=vs.100).aspx

 

나름 쉽게 풀어쓴다고 썼지만 나도 알아 먹기 힘드니 예제를 통해서 알아 보도록 하자

 

1. Unless overridden with __declspec(align(#)), the alignment of a scalar structure member is the minimum of its size and the current packing.

 

"__declspec(align(#))을 오버라이드 하지 않으면, 스칼라 구조체(long, bool과 같은 일반 변수로만 이루어진 구조체)의 멤버는 변수의 사이즈와 현재 지정된 byte padding align을 따른다."

 

이해를 돕기 위해 아래와 같이 두 가지 타입의 구조체를 만들어 보았다 :

struct Size_1_Align
{
    bool b;
};

 

struct Size_4_Align
{
    long l;
};


 

int main()

{

std::cout << "Size_1_Align:" << sizeof(Size_1_Align) << std::endl;
std::cout << "Size_4_Align:" << sizeof(Size_4_Align) << std::endl;

 

output :

Size_1_Align:1
Size_4_Align:4

bool 는 1 byte, long은 4 btye, 각 구조체의 사이즈는 각각의 멤버 변수 사이즈 대로 만들어진다.

 

2. Unless overridden with __declspec(align(#)), the alignment of a structure is the maximum of the individual alignments of its member(s).

 

"__declspec(align(#))을 오버라이드 하지 않으면, 구조체는 멤버 변수중 가장 사이즈가 큰 멤버 변수의 byte padding align을 따른다."

 

그럼 위의 Size_1_Align 구조체에는 2 byte 변수를, Size_4_Align 구조체에는 long(4byte) 보다 작은 사이즈의 멤버 변수를 추가해 보겠다. 가장 큰 사이즈 멤버 변수의 align을 따른다고 했으므로 Size_1_Align은 2 byte align 규칙을 따를 것이고, Size_4_Align 구조체는 4 byte align 규칙을 따를 것이다 :

struct Size_1_Align
{
    bool b;  // byte padding 규칙에 따라 요 부분은 1 byte가 더 채워 질 것입니다.
    short s;
};

struct Size_4_Align
{
    long l;
    bool b;
   short s;

};

int main()
{
    std::cout << "Size_1_Align:" << sizeof(Size_1_Align) << std::endl;
    std::cout << "Size_4_Align:" << sizeof(Size_4_Align) << std::endl;
}

output :
Size_1_Align:4
Size_4_Align:8

예상대로 각각 4 bypte 와 8 byte 가 나왔다.

 

3. A structure member is placed at an offset from the beginning of its parent structure which is the smallest multiple of its alignment greater than or equal to the offset of the end of the previous member.

 

"구조체 멤버는 부모 구조체의 byte padding align의 시작 offset에서 부터 이전 멤버 변수의 끝 offset 뒤에 자리 잡는다."

 

여기서 부모 구조체의 byte padding align에 대해서 잠깐 설명하자면, 부모가 4 byte padding 규칙을 가지고 실제 7 바이트 멤버만 가지고 있는 경우 7 byte 보다 큰 4 의 배수 byte offset을 시작 점으로 잡는다는 의미다. 예를 들어 :

struct Size_4_Align
{
    long l;
    short s; // bool b 와 위치가 바뀌었음.

   bool b; // short s 와 위치가 바뀌었음.
};

 

struct Derived_4_Align : public Size_4_Align
{
    char c;
};


int main()
{
    std::cout << "Size_4_Align:" << sizeof(Size_4_Align) << std::endl;
    std::cout << "Derived_4_Align:" << sizeof(Derived_4_Align) << std::endl;
}

 

output :
Size_4_Align:8
Derived_4_Align:12

Derived_4_Align 구조체는 Size_4_Align을 상속 받아 1 byte(char) 짜리 변수를 추가 했다. 생각해 보면 Size_4_Align의 경우 멤버 변수 나열이 4, 2, 1 byte로 나열 되었으므로 뒤에 1 바이트가 추가 되면 8 byte로 align이 될 수도 있지 않을까 생각했지만 에누리 없이 기존 8 byte에 더하여 (그리고 최대 사이즈 멤버 변수가 4 byte이므로) 12 byte 짜리 구조체가 되었다.

 

4. The size of a structure is the smallest multiple of its alignment larger greater than or equal the offset of the end of its last member.

 

"구조체의 사이즈는 가장 마지막 멤버 변수의 offset 을 기준으로 align 되는 byte의 최소 배수이며 offset과 같거나 큰 사이즈를 가진다."

 

말이 조금 달라지긴 했지만 위 3번에서 설명 했던 byte padding align과 같은 의미다. 결국 구조체의 사이즈는 byte padding을 통해 가장 페이징 하기 좋은 사이즈로 결정 된다는 의미다.

struct Size_8_Align
{
    long long ll;
    bool b;
};

 

int main()
{
    std::cout << "Size_8_Align:" << sizeof(Size_8_Align) << std::endl;
}

 

output :
Size_8_Align:16

결국 가장 큰 멤버 변수 사이즈(8 byte)로 align 되는 구조체는 1 byte가 더 추가 되더라도 8 byte 가 증가 하며 (align 사이즈의 배수) 실제 구조체 사이즈인 9 byte 보다는 크다는 것이다.

 

결론 :

오늘의 핵심은 구조체의 byte padding은 모든 구조체에 일괄적용 되는 것이 아니라 규칙에 따라 각각의 최적의 align 사이즈를 가지고 있다는 것이다.

 

위 코드들은 vc++ 2010과 gcc 4.4.6 버젼 위에서 테스트 해보았다(혹시나 예전 버젼 컴파일렁에서는 padding byte가 일괄 적용 된것이 아니었나 싶어 버젼을 명시한다).

Posted by kukuta

댓글을 달아 주세요

  1. Favicon of http://blog.ggamsso.wo.tc BlogIcon 깜쏘 2012.11.13 10:23  댓글주소  수정/삭제  댓글쓰기

    이게 플랫폼이 제한적이라면 상관이 없는데
    HP-UX, AIX, Solaris에서 다 다르게 SigBus 뜨면 미치죠.

  2. Favicon of http://kukuta.tistory.com BlogIcon kukuta 2012.11.14 23:34  댓글주소  수정/삭제  댓글쓰기

    뭔소리야? 패딩에 왠 sigbus? 구조체를 char 포인터로 강제 캐스팅해서 쓰고 있냐?
    아니면 HP, AIX, Solaris에서 바이트 패딩 룰이 다 다르다는 이야기야? 그래서 네트웍으로 전송할 때 값이 깨진단 의미??

  3. Favicon of https://jangjy.tistory.com BlogIcon 매직블럭 2014.07.23 17:07 신고  댓글주소  수정/삭제  댓글쓰기

    패딩 관련 문제로 매우 궁금하던차에 적절한 글 잘 봤습니다.

    감사합니다

" error: too few template-parameter-lists"는 신규 버젼 gnu cpp 컴파일러에서 발생하는 에러다.
이 문제는 템플릿 클래스의 static 멤버 변수를 초기화 할 때 발생하며, 해결을 template<>을 static 멤버 변수 초기화 코드 앞에 붙여 주어야 한다.

예를 들어 :


template <class T>

 class A
 {
    static int a;
    static const char * const name;
 };

와 같은 코드가 있다고 해보자. 예전에는 아래와 같이 써도 무방했다 :
 int A::a = 0;
const char * const A::name = NULL;
하지만 위와 같은 코드는 
CeePlusPlus 표준에 의해 이제는 더 이상 유효한 코드가 아니며 "" error: too few template-parameter-lists" 에러를 발생 한다.

아래와 같이 수정하도록 한다 :
 template<> int A<int>::a = 0;
 template<> const char * const  A<int>::name = "example";


Example code 2

 #include <iostream>
 template <int I>
 class B
 {
 public:
   static int b;
 };

template<> int B<1>::b = 1; template <int I> int B<I>::b = 0;

int main() { std::cout << B<1>::b << std::endl; // 1 std::cout << B<2>::b << std::endl; // 0 return 0;
} 
ref.
Too Few Template Parameter Lists : http://www.c2.com/cgi/wiki?TooFewTemplateParameterLists
Posted by kukuta

댓글을 달아 주세요

1. 라이브러리 파일 만들기

리눅스 상에서 GCC컴파일러를 이용해 라이브러리를 만드려면 다음과 같은 컴파일 과정을 통해서 오브젝트 파일을 생성한다.

$ gcc -c filename.c (.cpp 파일을 컴파일 할 경우 g++ -c filename.cpp)

 참고 : (GCC 옵션 보기) http://kukuta.tistory.com/51

컴파일이 제대로 끝났다면 확장자가 .o인 filename.o파일이 만들어 질것이다. 이번에는 생성된 오브젝트 파일을 이용하여 라이브러리를 만들도록 한다.

이 때 사용하는 명령어는 "ar"로 라이브러리 작성에 사용된다. 아래와 같이 ar옵션 그리고 생성하고자 하는 라이브러리의 이름, 마지막으로 오브젝트 파일들의 이름을 나열한후 실행 한다.

(※ 윈도우에서 라이브러리 파일의 확장자는 .lib이지만 리눅스에서는 .a확장자를 지닌다.)

$ ar crv libfile.a filename.o

2. 라이브러리 파일의 사용

라이브러리 파일을 이용하여 실행 파일을 만들때 컴파일러의 옵션을 이용하는 방법이 있다. 이때 사용되는 옵션이 '-L' 과 '-l' 인데, -L의 경우에는 사용하고자 하는 라이브러리가 포함된 디렉토리 명을 명시하는 옵션이고, -l은 라이브러리의 이름을 적어주는 옵션이다. 이때 라이브러리의 이름은 lib라는 말과 확장자 .a를 생략하여 사용할수 있다. 즉, libfile.a의 경우 그냥 file이라고 쓰면 된다.

$ gcc -o executefile uselib.c -L. -l file

이때 만일 라이브러리 파일이 /home/user 디렉토리 밑에 있다면 -L 옵션을 다음과 같이 주면 된다.

-L /home/user

출처 : 이것이 C++이다 : 영진 닷컴 : 신재호

'도구의발견' 카테고리의 다른 글

vim 문자열 검색 및 치환  (2) 2007.10.25
서비스 포트 변경  (0) 2007.05.09
gcc 라이브러리 만들기와 사용  (0) 2007.04.24
gcc의 실행과 옵션  (0) 2007.04.24
cvs 사용법  (0) 2007.03.03
vim editor - foldmethod  (1) 2006.12.12
Posted by kukuta
TAG C/C++, GCC

댓글을 달아 주세요

GCC의 실행

gcc [-o outputfilename] [-c] [-g] [-O | -O2] [-Idirectory] [-Ldirectory] [-llibrary] sourcefilename

 gcc를 실행하기 위해서는 반드시 소스 파일을 적어주어야 하며 나머지 사항들을 컴파일을 위한 옵션들로 필요에 따라 적거나 생략할 수 있습니다. 또한 모든 옵션은 적는 순서와 상관이 없이 똑같은 기능을 합니다.

GCC의 옵션

  • -c
     이 옵션을 사용하면 링크의 과정은 생략되고 컴파일만 수행합니다. 만약 이 옵션이 사용된다면 링크와 관련된 -l 또는 -L 옵션은 무시됩니다.
  • -o outputfilename
     컴파일한 결과를 저장하는 파일명을 직접 지정해 주는 옵션입니다. 이 옵션이 사용되지 않으면 기본적으로 a.out이라는 실행 파일이 생성되며, -c 옵션이 주어졌을 때는 sourcefilename.o 파일이 생성됩니다. 기본적으로 생성되는 파일명을 원하지 않을 경우 이 옵션을 사용하여 바꿔주면됩니다.
  • -g
     실행파일을 생성할 때, 디버거를 위한 변수테이블을 함께 생성해 준다. 만약 이 옵션을 사용하지 않으면, 디버거를 사용할 수 없습니다.
  • -O 또는 -O2
     optimization을 지시해주는 옵션입니다. -O를 사용하였을 때보다 -O2를 사용하면 프로그램의 크기가 더 줄어듭니다. 물론 아무것도 사용하지 않은 것보다는 당연히 크기가 줄어들겠지요? 최적화에 대한 자세한 내용은 ``GCC-개요''의 ``최적화'' 기사를 참조하시기 바랍니다. 가끔은 -O2를 사용하면 프로그램의 수행 속도가 현저히 저하되기도 합니다. (특히 GCC의 옛날 버전)
  • -Idirectory
     이 옵션은 header파일을 찾는 root디렉토리를 적어주는 옵션입니다. 만약 C source파일에서

    #include <myheader.h>

    라고 적어 주었을 때, 만약 /usr/local/include 나 /usr/include 또는 GCC설치디렉토리/include 에 위치하고 있지 않다면 header파일을 찾을 수 없다는 에러를 발생시키게 되는데, 이 -I 옵션을 사용하여 myheader.h 파일이 위치한 디렉토리를 적어주면 header파일을 잘 찾을 수 있게 된다. 이 옵션은 프로그램의 크기가 커져서 현재 디렉토리가 아닌 곳에 header파일들을 모아 두었을 때나, X-window 프로그래밍과 같이 C의 표준 제공 라이브러리가 아닌 추가된 파이브러리의 header파일들이 저장된 디렉토리를 지정할 때 유용하게 사용될 수 있다.

  • -Ldirectory
     이 옵션은 ld 인 링커프로그램에 의해서 참조된다. *.so.*의 이름을 갖는 동적라이브러리나 *.a 또는 *.sa.*의 이름을 갖는 정적라이브러리들이 위치한 디렉토리가 /usr/lib 나 /usr/local/lib 또는 GCC설치디렉토리 가 아닌 경우 이곳에 그 디렉토리 이름을 적어주어야 만이 제대로 링크가 된다. 이 옵션을 잘 지정해 주지 못하면
    Undefined first referenced symbol in file test_me /var/tmp/cca001l81.o 
    ld: fatal: Symbol referencing errors. No output written to a.out
    와 같은 에러가 나온다.
  • -llibraryname
     -L 옵션과 함께 사용되면서 사용할 라이브러리를 직접 지정해 주는 것이다. lib*.a 의 이름을 갖는 라이브러리에서 `*'부분을 libraryname부분에 적어주면 된다. 예를 들어 sin()이나 cos() exp()와 같은 수학함수를 사용하였을 경우 ``-lm'' 이라고 적어주어야만 바로 위의 -L옵션 설명에서 나왔던 것과 같은 에러가 발생하지 않는다. -L옵션과 마찬가지로 이 옵션을 잘못 설정하면 같은 ``Undefined symbol''에러가 발생한다.
  • -Ddefine
     
    디버를 위해 소스내에 #ifdef이라는 코드를 자주 삽입 하곤한다. 하지만 매번 컴파일 할 때 마다 #define문구를 넣었다 뺏다 하는 것은 상당히 귀찮은 일이다. -D 옵션을 사용하면 소스 내에 마치 #define으로 선언 해 놓은 것과 같은 효과를 볼 수 있다.

실행 예제

gcc -g -I/usr/dt/include -I/usr/openwin/include -I/usr/ucbinclude -L/usr/dt/lib -L/usr/openwin/lib -L/usr/ucblib -D_NO_PROTO -c run.c
gcc -o vision -g -I/usr/dt/include -I/usr/openwin/include -I/usr/ucbinclude -L/usr/dt/lib -L/usr/openwin/lib -L/usr/ucblib -D_NO_PROTO graphic.o image_display.o image_load.o view.o memory.o dsp.o run.o -lXm -lXt -lXext -lX11 -lsocket -lgen -lm

 인클루드나 라이브러리 옵션을 동시에 여럿 선언할 경우 앞에서 부터 참조하게 된다. 그러므로 만약 똑같은 이름의 헤더파일이나 라이브러리 파일이 여러군데 존재할 경우 이 옵션을 사용해서 사용자가 우선순위를 정해줄 수 있다. 첫줄의 명령어는 run.c를 컴파일해서 run.o 라는 오브젝트 파일을 만들어 주는 것이고, 아랫줄의 명령어는 image_display.o ~ run.o 파일들을 Xm, Xt, Xext, X11, socket, gen, m 의 라이브러리를 사용해서 링크시켜주는 명령어 입니다. (참고로 이 명령 줄은 make에 의해서 자동으로 입력된 내용입니다.)

참 고 : http://blog.naver.com/nillwow/5794785

mail : kukuta@gmail.com

'도구의발견' 카테고리의 다른 글

서비스 포트 변경  (0) 2007.05.09
gcc 라이브러리 만들기와 사용  (0) 2007.04.24
gcc의 실행과 옵션  (0) 2007.04.24
cvs 사용법  (0) 2007.03.03
vim editor - foldmethod  (1) 2006.12.12
awk  (0) 2006.12.12
Posted by kukuta
TAG C/C++, GCC

댓글을 달아 주세요