블로그 이미지
대갈장군

calendar

1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31

Notice

'프로그래밍/C '에 해당되는 글 4

  1. 2015.02.12 C Language Constructors and Destructors with GCC
  2. 2010.09.25 C Standard Library
  3. 2010.03.11 싱글 스레드에서 멀티 스레드로... 왜 /MD 인가?
  4. 2009.07.28 호출 규약
2015. 2. 12. 04:06 프로그래밍/C

Source: http://phoxis.org/2011/04/27/c-language-constructors-and-destructors-with-gcc/


Constructors and Destructors are special functions. These are one of the features provided by an Object Oriented Programming language. Constructors and Destructors are defined inside an object class. When an object is instantiated, ie. defined of or dynamically allocated of that class type, the Constructor function of that class is executed automatically. There might be many constructors of which the correct implementation is automatically selected by the compiler. When this object is destroyed or deallocated, the Destructor function is automatically executed. For example when the scope of the object has finished or the object was dynamically allocated and now being freed. The Constructors and the Destructors are generally contains initialization and cleanup codes respectively required by an object to operate correctly. Because these functions are automatically invoked by the compiler therefore the programmer freed from the headache of calling them manually.

There is no such thing called ‘constructors’ and ‘destructors’ in C programming language or in structured languages, although there is no boundaries on defining such functions which act like them. You need to make functions which act like the constructors and destructors and then call them manually.

The GCC constructor and destructor attributes

GCC has attributes with which you can tell the compiler about how a lot of things should be handled by the compiler. Among such attributes the below function attributes are used to define constructors and destructors in C language. These would only work under GCC. As there is no objects and class approach in C the working of these functions are not like C++ or other OOP language constructor and destructors. With this feature, the functions defined as constructor function would be executed before the function main starts to execute, and the destructor would be executed after the main has finished execution. The GCC function attributes to define constructors and destructors are as follows:

1
2
3
4
__attribute__((constructor))
__attribute__((destructor))
__attribute__((constructor (PRIORITY)))
__attribute__((destructor (PRIORITY)))

For example, to declare a function named begin () as a constructor, and end () as a destructor, you need to tell gcc about these functions through the following declaration.

1
2
void begin (void) __attribute__((constructor));
void end (void) __attribute__((destructor));

An alternate way to flag a function as a C constructor or destructor can also be done at the time of the function definition.

1
2
3
4
5
6
7
8
__attribute__((constructor)) void begin (void)
{
 /* Function Body */
}
__attribute__((destructor)) void end (void)
{
 /* Function Body */
}

After declaring the functions as constructors and destructors as above, gcc will automatically call begin () before calling main () and call end () after leaving main or after the execution of exit () function. The following sample code demonstrates the feature.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
 
void begin (void) __attribute__((constructor));
void end (void) __attribute__((destructor));
 
int main (void)
{
  printf ("\nInside main ()");
}
 
void begin (void)
{
  printf ("\nIn begin ()");
}
 
void end (void)
{
  printf ("\nIn end ()\n");
}

Execution of this code will come up with an output which clearly shows how the functions were executed.

1
2
3
In begin ()
Inside main ()
In end ()

Multiple Constructors and Destructors

Multiple constructors and destructors can be defined and can be automatically executed depending upon their priority. In this case the syntax is __attribute__((constructor (PRIORITY))) and __attribute__((destructor (PRIORITY))). In this case the function prototypes would look like.

1
2
3
4
5
6
7
8
void begin_0 (void) __attribute__((constructor (101)));
void end_0 (void) __attribute__((destructor (101)));
 
void begin_1 (void) __attribute__((constructor (102)));
void end_1 (void) __attribute__((destructor (102)));
 
void begin_2 (void) __attribute__((constructor (103)));
void end_2 (void) __attribute__((destructor (103)));

The constructors with lower priority value would be executed first. The destructors withhigher priority value would be executed first. So the constructors would be called in the sequence: begin_0begin_1 ()begin_2 () . and the destructors are called in the sequence end_2 ()end_1 ()end_0 (). Note the LIFO execution sequence of the constructors and destructors depending on the priority values.

The sample code below demonstrates this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
 
void begin_0 (void) __attribute__((constructor (101)));
void end_0 (void) __attribute__((destructor (101)));
 
void begin_1 (void) __attribute__((constructor (102)));
void end_1 (void) __attribute__((destructor (102)));
 
void begin_2 (void) __attribute__((constructor (103)));
void end_2 (void) __attribute__((destructor (103)));
 
int main (void)
{
  printf ("\nInside main ()");
}
 
void begin_0 (void)
{
  printf ("\nIn begin_0 ()");
}
 
void end_0 (void)
{
  printf ("\nIn end_0 ()");
}
 
void begin_1 (void)
{
  printf ("\nIn begin_1 ()");
}
 
void end_1 (void)
{
  printf ("\nIn end_1 ()");
}
 
void begin_2 (void)
{
  printf ("\nIn begin_2 ()");
}
 
void end_2 (void)
{
  printf ("\nIn end_2 ()");
}

The output is as below:

1
2
3
4
5
6
7
In begin_0 ()
In begin_1 ()
In begin_2 ()
Inside main ()
In end_2 ()
In end_1 ()
In end_0 ()

Note that, when compiling with priority values between 0 and 100 (inclusive), gcc would throw you warnings that the priority values from 0 to 100 are reserved for implementation, so these values might be used internally that we might not know. So it is better to use values out of this range. The value of the priority does not depend, instead the relative values of the priority is the determinant of the sequence of execution.

Note that the function main () is not the first function/code block to execute in your code there are a lot of code already executed before main starts to execute. The function main is the user’s code entry point, but the program entry point is not the main function. There is a startup function which prepares the environment for the execution. The startup functions first call the functions declared as constructors and then calls the main. When main returns the control to the startup function it then calls those functions which you have declared as the destructors. There are separate sections in the executable .ctors and .dtors which hold these functions. (not discussed here).

As there is no class object creation in C language does not have such features like in C++ or other OOP languages but this feature can bring some flexibility by calling the functions automatically on execution and termination of the code which you needed to do inside the main. For example one use may be like, you have a dynamically allocated global variable, might point to a linked list head or an array, or a file descriptor which you can allocate inside a constructor. If some error is encountered you can immediately call exit () or the program terminates normally, depending on the error code you can make cleanup in the destructors.

Another good use is probably calling the initialization functions of some library, a bunch of which needs to be called in each program. You can make a separate file with the constructor files calling properly the library functions (probably operating on globals) and calling the library cleanup functions in the destructors. Whenever you make a program what you need to do is to compile your code with this file containing the constructors and destructors and forget about calling the initialization and cleanup functions in the main program. But doing this will make your code unportable, as it would only work under GCC.

결론:

__attribute__ 속성을 사용해서 C에서도 생성자와 파괴자 역활을 하는 함수를 선언하여 사용할 수 있다. main 함수가 호출되기 전에 자동으로 GCC가 __attribute__ 로 선언된 생성자 함수를 호출해 주고 main이 끝나는 곳에서 다시 __attribute__로 선언된 파괴자 함수를 호출하여 마무리.


장점:

1. 이런식으로 C에서 C++의 생성자 / 파괴자 흉내를 낼 수 있다

2. 사용 예로는 동적으로 선언되는 전역 변수를 할당 및 해제를 원큐에 해결 가능 (메모리 해제 걱정 NO!)

3. 에러를 만날 경우 즉각 exit를 호출하여도 자동으로 파괴자가 호출되므로 자원 해제가 용이함

4. 여러개의 프로그램에서 같은 라이브러리를 사용하는 경우, 생성자로 main 진입 전에 라이브러리를 초기화 시켜 주고 파괴자에서 해제해 주면 아주 굿!


단점:

GCC에서만 작동하므로 소스 코드가 portable이 아니다. 뭐, 그거야 당연한 거지만..


'프로그래밍 > C' 카테고리의 다른 글

C Standard Library  (0) 2010.09.25
싱글 스레드에서 멀티 스레드로... 왜 /MD 인가?  (0) 2010.03.11
호출 규약  (0) 2009.07.28
posted by 대갈장군
2010. 9. 25. 01:40 프로그래밍/C
우선 역사를 보자. C 언어는 기준이 완성되기 전까지는 Input/Output 기능과 같은 기본 함수를 언어자체적으로 지원하지 않았다. 그것에 불편을 느낀 다수의 사용자들이 모여 지금의 C Standard Library를 작성하게 되었다. 

Unix와 C는 둘 다 AT&T's Bell Laboratories에서 1960년대 그리고 1970년대에 개발되었는데 C 언어가 인기가 높아지고 많은 사람들이 사용하기 시작하면서 호환성 문제가 발생한다. 고로 American National Standards Institute(ANSI)에서 표준을 세우고 규격화하여 내놓은 것이 바로 ANSI C이다. 그리고 이것은 1989년에 완성된어 C89라고 명명되어 졌다. 

그렇다면, C Standard Library는 뭐냐 하면, C Programming 언어에서 사용되는 기본 명령들, 예를 들자면 input/output 및 string 핸들링 함수와 같은 것들을 구현하기 위한 headers와 library routines를 모아놓은 ISO C 기준의 일부분이다. 

즉, 다시 말해서 C Standard Library는 문서에 의해 규정된 '인터페이스 기준'이다. 고로 걍 설명서일 뿐이라는 것. 사실 C Standard Library는 24개의 헤더 파일인데 요 헤더 파일들에는 함수들과 데이터 타입 그리고 매크로등이 들어가 있다. 

이것은 다른 언어 (예를 들자면 자바)에 비교해서 아주 간단한 함수들만 선언된 헤더파일들이다. C Standard Library는 기껏해야 수학 함수, 문자열 함수, 타입 변환 함수 및 파일 입출력, 콘솔 화면 입출력 함수 정도만 명시되어 있다. 고로 C Standard Library는 C++ Standard Template Library와 같은 고급 컨테이너 타입이나 GUI, 네트워킹 툴등의 고급 기능은 전혀 없다.

아래의 목록이 바로 ISO가 명시하는 C Standard Library의 헤더 파일들이다.

ISO C library headers

NameFromDescription
<assert.h> Contains the assert macro, used to assist with detecting logical errors and other types of bug in debugging versions of a program.
<complex.h> C99 A set of functions for manipulating complex numbers.
<ctype.h> Contains functions used to classify characters by their types or to convert between upper and lower case in a way that is independent of the used character set (typically ASCII or one of its extensions, although implementations utilizing EBCDIC are also known).
<errno.h> For testing error codes reported by library functions.
<fenv.h> C99 For controlling floating-point environment.
<float.h> Contains defined constants specifying the implementation-specific properties of the floating-point library, such as the minimum difference between two different floating-point numbers (_EPSILON), the maximum number of digits of accuracy (_DIG) and the range of numbers which can be represented (_MIN_MAX).
<inttypes.h> C99 For precise conversion between integer types.
<iso646.h> NA1 For programming in ISO 646 variant character sets.
<limits.h> Contains defined constants specifying the implementation-specific properties of the integer types, such as the range of numbers which can be represented (_MIN_MAX).
<locale.h> For setlocale and related constants. This is used to choose an appropriate locale.
<math.h> For computing common mathematical functions.
<setjmp.h> Declares the macros setjmp and longjmp, which are used for non-local exits.
<signal.h> For controlling various exceptional conditions.
<stdarg.h> For accessing a varying number of arguments passed to functions.
<stdbool.h> C99 For a boolean data type.
<stdint.h> C99 For defining various integer types.
<stddef.h> For defining several useful types and macros.
<stdio.h> Provides the core input and output capabilities of the C language. This file includes the venerable printffunction.
<stdlib.h> For performing a variety of operations, including conversion, pseudo-random numbers, memory allocation, process control, environment, signalling, searching, and sorting.
<string.h> For manipulating several kinds of strings.
<tgmath.h> C99 For type-generic mathematical functions.
<time.h> For converting between various time and date formats.
<wchar.h> NA1 For manipulating wide streams and several kinds of strings using wide characters - key to supporting a range of languages.
<wctype.h> NA1 For classifying wide characters.



posted by 대갈장군
2010. 3. 11. 00:29 프로그래밍/C
예전에 다른 글에서 Visual Studio의 Code Generation 옵션에 대해서 이야기 한 적이 있다. 

위 글에서는 간단하게 옵션에 대한 설명만 했지만 왜 싱글 스레드에서 멀티 스레드로 바뀌었는지에 대해서는 충분히 설명하지 못했다. 그래서 이 글을 추가로 작성한다.

C 언어는 멀티 스레드 개념이 생기기 이전의 언어이므로 멀티 스레드 환경에서 발생하는 자원 경쟁 상태 (Race Condition)을 염두해 두지 않았다. 즉, 여러개의 스레드가 하나의 전역 변수를 공유하여 서로 바꾸려고 할때 발생하는 경쟁 상태를 말하는데 이것의 대표적인 예로 C 언어의 errno 전역 변수이다.

이 전역 변수는 호출한 함수의 결과를 저장하는 전역 변수인데 싱글 스레드의 경우에는 아무런 문제가 없으나 멀티 스레드의 경우 임의의 함수 호출 후 다른 스레드가 또 다른 함수를 호출하여 그 결과를 errno에 저장해 버릴 수 있기 때문에 100% 신뢰할 수 있는 결과를 가지지 않는다. 

그래서 Microsoft사에서 내놓은 해결책이 멀티 스레드를 위한 새로운 런타임 라이브러리 사용이었다. 멀티 스레드를 위한 런타임 라이브러리는 멀티 스레드에서도 안정적인 C 함수들을 호출하므로 100% 신뢰할 수 있다. 


내가 늘 불만인 것은 Visual Studio는 이렇게 중요한 옵션에 대한 설명이 너무나 부족하다는 것이다. 초보 프로그래머는 절대로 "왜 이걸 써야 하지?" 라는 궁극적인 질문에 대답을 할 수 없다.

MSDN에 설명이 되어 있다고 하지만 그 마저도 C 언어를 안다는 가정하에 설명하고 있어서 읽다가 짜증만 난다. 쉽게 쉽게 풀어서 설명해 주면 안되겠니? 응?

아무튼, 배포를 위한 프로그램을 개발한다면 거의 /MD를 사용하면 된다. D가 의미하는 것이 다이나믹 링크이므로 프로그램을 설치할 컴퓨터에 Visual Studio C++ 런타임 라이브러리만 잘 설치되어 있으면 프로그램은 무리 없이 돌아갈 것이다.

하지만 한가지 더 중요한 것은 이렇게 /MD 설정으로만 모든 것이 해결되는 것은 아니며 스레드 생성시 반드시 _beginthreadex() 함수를 사용해라는 것인데, 이 함수는 스레드 생성 전에 스레드를 위한 독립 저장 공간을 생성하여 자원 경쟁을 예방한다. 이런 독립 공간을 스레드 지역 저장소라하며 원어로를 Thread Local Storage 라 부른다. 

그리고 생성을 저 함수로 했다면 스레드를 죽일때에는 _endthreadex()를 사용해야 한다. 

결론적으로,
  • C 언어는 싱글 스레드를 근간으로 작성되었으므로 멀티 스레드의 자원 경쟁 문제를 유발할 수 있다.
  • 그런 발생 가능한 문제 제거를 위해서는 멀티 스레드 런타임 라이브러리 옵션 (/MD)를 사용하면 된다.
  • 완벽한 문제 발생 제거를 위해서는 _beginthreadex()_endthreadex() 함수로 스레드를 생성하고 해제하라.


'프로그래밍 > C' 카테고리의 다른 글

C Language Constructors and Destructors with GCC  (0) 2015.02.12
C Standard Library  (0) 2010.09.25
호출 규약  (0) 2009.07.28
posted by 대갈장군
2009. 7. 28. 01:43 프로그래밍/C
호출 규약은 함수를 호출하는 방식에 대한 약속을 정의한 것인데 예를 들자면 호출하는 쪽에서 메모리를 정리하는지, 인수는 어떻게 전달 할 것인지, 그리고 리턴 값은 어떻게 반환할 것인지 등을 정의 할 수 있다.

함수를 호출할 때는 스택(Stack)을 사용한다. 알다시피 스택은 메모리 영역의 아래부분에 위치하여 LIFO (Last in First out)의 원칙대로 동작하며 항상성을 유지한다. 

함수가 호출될 때 스택에는 함수로 전달되는 인수, 실행을 마치고 복귀할 주소, 그리고 지역 변수의 정보등이 저장되는데 이것을 스택 프레임 (Stack Frame)이라고 한다. 

이제 C에서 사용하는 각종 호출 규약을 살펴보자. 

총 다섯 가지가 있다. __cdecl, __stdcall, __fastcall, thiscall, naked 이다.

아마도 Visual Stuido를 사용해본 사람이라면 __cdecl과 __stdcall 그리고 thiscall을 자주 봤을 것이다. (에러 메세지에서)

__fastcall과 naked는 C / C++ 에서는 거의 사용하지 않기 때문에 설명이 필요없다. 원래 __fastcall은 레지스터 공간을 이용해 인수를 전달함으로써 좀더 빠른 속력을 내고자 했으나 범용성이 떨어져서 사용이 힘들고 naked는 C나 C++이 아닌 다른 언어로 작성된 함수 호출시 호출 규약을 정하지 않고 사용할 때 붙이는 접미사인데 이 역시도 나에게는 거의 사용 가능성이 0%다.

그러면 남은 것이 3가지인데 이중에서 thiscall은 멤버 함수이면 자동으로 붙게되는 호출 규약이다. 이 호출 규약이 붙으면 자동으로 객체의 포인터가 인수로 전달된다는 점이 특별하다. 

이 놈은 사용자가 임의로 가져다 쓸수 있는 선언자가 아니고 컴파일러가 알아서 붙여주는 '호출규약'일 뿐이므로 이 마저도 제외해야 한다. 결국 남는것은 딱 두개. __cdecl과 __stdcall이다.

이 두놈의 큰 차이점 두가지는,

1. 스택을 정리하는 주체가 다르다는 점이다. __cdecl은 호출한 쪽에서 정리하고 __stdcall은 호출된 함수측 에서 스택을 정리한다.

2. 가변 인수 함수라면 무조건 __cdecl을 사용해야 한다. 왜냐면 __stdcall은 함수측에서 스택을 정리해야 하는데 가변인수이므로 인수가 몇개나 들어올지 미리 알 길이 없다. 그래서 가변 인수 함수의 경우는 무조건 __cdecl을 사용해야 한다. 

호출 규약이 불일치 하는 경우에 발생하는 문제점은 치명적이다. 헌데 이런 오류가 발생하기는 상당히 힘든데 C로 작성해서 C로 호출하는 경우 호출 규약이 불일치 할 경우는 거의 없기 때문이다.

아무튼 어찌 어찌 해서 호출 규약이 서로 불일치하게 되면 호출한 쪽에서는 함수쪽에서 스택을 해제할 것이라 굳게 믿고, 반대로 함수측에서는 호출한 쪽에서 스택을 해제할 것이라 굳게 믿게되는 이상한 상황이 발생한다.

그렇게 되면 항상성을 잃게 되고 메모리에 오류가 발생하여 99.99992% 확률로 다운될 것이다. 다운이 안되더라도 엉망징찬이 될 게 뻔하다. 

아무튼, VS 2005를 사용하여 프로그램을 작성하다 보면 에러 메세지에 __cdecl과 __stdcall등 다양한 호출 규약의 접미어가 붙어 있는 것을 보게 되는데 이것을 보고 당황할 필요가 없다는 것이다. :)
posted by 대갈장군
prev 1 next