라벨이 C인 게시물 표시

C: rand() 와 srand() 로 난수 만들기

C 언어에서 난수를 만들 때 가장 널리 쓰이는 함수는 rand() 와 srand() 이다. 이 글에서는 이 두 함수의 사용법과 활용법을 알아보고 한계와 대안에 대해서 다루려고 한다.   1. rand() rand() 함수의 원형은 다음과 같다. #include <stdlib.h> int rand(void); rand() 함수는 [0, RAND_MAX] 범위(0과 RAND_MAX 포함)에서 정수 형태의 의사-난수(pseudo-random number)를 만든다. RAND_MAX 는 rand() 가 만드는 난수의 최대값으로, C 라이브러리에 따라 다르다. 다만, 최소 크기는 0x7FFF(=32767, 15비트)로 보장된다.   1-1. 원하는 범위내의 난수 만들기 rand() 함수는 [0, RAND_MAX] 범위의 난수를 만든다. 하지만, 실제로 필요한 범위는 이와는 다르다. 예를 들어, [10, 20] 사이의 난수를 만들려면 어떻게 해야 할까?  보통 두 가지 방법이 알려져 있다. 하나는 나머지 연산(%) 을 이용하는 것이고, 다른 하나는 비례 관계 를 이용하는 것이다.   1-1-1. 나머지 연산을 이용하는 방법 나머지 연산을 이용하는 방법부터 알아보자. 나머지 연산을 이용하면 [0, n) 범위(0 포함, n 비포함)를 얻을 수 있다. 예를 들어 n=7 이라면, 어떤 값이 오더라도 7로 나눈 나머지는 결국 0 이상 6 이하의 정수이다. 여기에 적당한 값을 더해 주면 우리가 원하는 범위의 난수를 얻을 수 있다. 이를 함수로 나타내면 다음과 같다. Colored By Color Scripter ™ 1 2 3 4 int  randrange( int  a,  int  b) {      return  (rand() % (b - a + 1)) + a; } 하지만, 이...

C: alloca() 의 함정

C 언어로 프로그래밍을 하면서 가장 불편했던 것 중의 하나가 메모리 할당과 해제였다. 할당은 하되 알아서 해제해 주면 얼마나 좋을까? 라는 생각을 많이 했었다. C++ 같은 언어는 다양한 기법이 개발되면서 이에 대한 문제를 대폭 해소할 수 있었지만... 이런 불편을 많은 사람들이 공감했는지 비표준적인 방식으로 나름의 해결방법을 제안했다. 그 중의 하나가 alloca() 이다. C 언어에서 메모리를 할당하면 꼭 해제해야 하는 이유는 메모리가 힙에 할당되기 때문이다. 그래서 alloca() 는 메모리를 스택에 할당한다. 함수가 끝나면 스택은 자동으로 복구가 되니까. 문제는 여기에서 발생한다. alloca() 로 할당한 메모리는 함수가 끝날 때까지 살아 있다. 어디에서 할당하든 함수가 끝날 때까지 상태가 유지된다. 만약 alloca() 를 반복문 안에서 사용하면 어떻게 될까?  보통 alloca() 를 반복문 안에서 사용하는 이유는 다음 두 가지 기대 때문일 것이다. 첫째, 반복문 범위(scope) 안에서 자동 소멸할 것이다. 둘째, alloca() 로 할당된 메모리는 함수가 끝날 때까지 유효하므로 반복문 안에서 한 번만 할당할 것이다. 하지만, 두 가지 모두 잘못된 생각이다. alloca() 로 할당된 메모리가 해제되는 기준은 함수가 끝날 때이지, 특정 범위가 끝날 때가 아니고, alloca() 는 호출될 때마다 스택에 메모리 공간을 할당한다. 결국 어떤 결과가 생길까? 바로 스택 공간 부족(out of stack space)이다. 반복문 내에서 계속 스택 공간을 소모하기 때문이다. 따라서 alloca() 는 반복문 안에서 쓰면 안된다. 대신에 C99 을 쓴다면 VLA(Variable-length Array)를 쓰자. VLA 는 alloca() 와 달리 변수가 선언된 범위가 끝날 때 자동으로 소멸한다. 반복문 안에서 쓰더라도 alloca() 와 같은 문제는 발생하지 않는다. 그런데 VLA 에 대해 유의할 것은 C11 에서는 필수가 아닌 선택으로 바뀌었다...

C: gcc 에서 매크로를 함수처럼 쓰기

C 에서 매크로는 매우 강력한 기능을 제공한다. 하지만 몇 가지 아쉬운 점이 있는데, 매크로를 일반 함수처럼 쓰기에는 어려움이 있다는 것이다. 예를 들어, 어떤 정수 m 의 어떤 정수 n 제곱을 구하는 기능을 매크로로 구현하기란 쉽지 않다. 왜냐하면 거듭제곱의 결과값을 저장할 또 다른 변수가 필요하고, 반복문이 있어야 하며, 그 결과를 돌려주려면, 결국 복합문이 필요하기 때문이다. 물론 do{}while(0) 같은 기법을 써도 되지만, 아쉽게도 이것으로는 값을 되돌려줄 수가 없다. gcc 는 이를 위해 statement expression 이라는 확장 기능을 제공한다. 위의 기능을 제공하는 매크로를 POW() 라고 하자. POW() 는 다음처럼 쓸 수 있다. Colored By Color Scripter ™ 1 2 3 4 5 6 7 8 9 #define  POW( m, n ) ({      \      int  POW_m = (m);        \      int  POW_n = (n);        \                             \      while ( POW_n-- > 0 )  ...

C: 이름없는 임시 변수 만들기

프로그래밍을 하다보면 때때로 임시 변수가 필요할 때가 있다. 특히 어떤 함수를 호출할 때 어쩔 수 없이 넘겨야 하는 포인터 인자 때문에 변수가 필요한 경우가 있다. 예를 들어, 어떤 디스크립터가 소켓인지 아닌지 판단하기 위해 getsockopt() 를 쓰는 경우가 있다. getsockopt() 의 원형은 다음과 같다. int getsockopt(int socket , int level , int optname,        void * optval , socklen_t * optlen ); 소켓 여부를 판단하기 위해서는 socket 과 level 그리고 optname이 필요하지만, optval 이나 optlen 은 실질적으로 필요하지 않다. 일반적인 경우라면 다음과 같은 형태의 코드가 펼쳐질 것이다. Colored By Color Scripter ™ 1 2 3 4 5 6 7 8 9 10 {      int  sock;      int  optval = 0;      int  optlen =  sizeof ( optval );      if ( getsockopt( sock, SOL_SOCKET, SO_TYPE, &optval, &optlen ) == ENOTSOCK )     {          /* Not a socket */     ...

C/C++: __LINE__, __FILE__, __FUNCTION__ 의 활용

C/C++ 프로그램이 오류를 일으켰을 때, 오류의 원인을 파악하기 위해 필요한 정보들이 있다. 파일 이름, 함수 이름, 줄 번호 따위이다. 다행히, C/C++ 컴파일러는 컴파일러 수준에서 이러한 정보들을 제공하고 있다. 바로 __FILE__, __FUNCTION__, __LINE__ 따위가 그것이다. 이름에서 알 수 있듯이, __FILE__ 은 파일이름, __FUNCTION__ 은 함수 이름, __LINE__ 은 줄번호를 나타낸다. 여기에서 주의할 점은 __FILE__ 과 __LINE__ 은 문자열 상수 매크로이지만, __FUNCTION__ 은 컴파일러에 따라 또는 컴파일 모드(C 모드인지 C++ 모드인지)에 따라 문자열 상수 매크로일 수도 있고 변수일 수도 있다. 매크로인지 변수인지에 따라 무엇이 달라지는 것일까 ? 만일 매크로라면 다음과 같은 문법이 가능하다. Colored By Color Scripter ™ 1 const   char  *str =  "str is in "  __FUNCTION__; 하지만 변수라면 컴파일러는 에러라며 불평할 것이다. 따라서 __FUNCTION__ 은 변수라고 가정하 고 프로그래밍하는 것이 컴파일러의 특성에 의존하지 않는 가장 안전한 방법이다. 그리고 __FUNCTION__ 하위 호환성을 위해 제공되는 것이고, c99 을 지원하는 컴파일러에서는 __func__ 도 쓸 수 있다. 물론, __func__ 는 변수다. 이것들을 어떻게 사용할까? 프로그래머마다 다르겠지만, 내 경우에는 진입/탈출 로그를 만들기 위해 많이 사용한다. 다음은 실제 사용예이다. Colored By Color Scripter ™ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include  <stdio.h> #define  ...