3.23(금) clock() 함수 응용 사례.

from Study/C언어 2007/03/23 16:12 view 22157

#include <Turboc.h>

//typedef long clock_t;
//#define CLOCKS_PER_SEC 1000

void
main()
{
    clock_t t1,t2;
    int count=0;

    t1=clock();

    for (;;) {

        printf("기다리십시오. %d\n",count++);

        t2=clock();

        if (t2-t1 > 3*CLOCKS_PER_SEC) {

            break;
        }
    }

    printf("끝났습니다.\n");
}

3초간 어떤 작업을 반복적으로 수행 하고 싶을 때 쓸 수 있는 코드 이다.

시작시간과 현재 진행된 시간차가 3*1000,즉 3초일 때 루프벗어 난다. Delay는 기다리는 동안

다른 일을 할 수 없지만 일정 시간동안 어떤 작업을 하고 싶다면 clock 함수로 구한 시간을 이용하여

두 시간값의 차(t2 – t1)을 구해준다.

Tag |

#include <TurboC.h>
#include <math.h>

void
main()
{
    int i;
    time_t t1,t2;

    time(&t1);
    for( i = 0; i < 100000 ; i++)
        printf("결과= %f\n", sin(i*3.1416/180)*(i*3.1416/180));
    time(&t2);
    printf("%.2f초가걸렸습니다.", difftime(t2,t1));
 
}

Time함수와 difftime함수를 통해서 돌려봤더니 속도 차이가 엄청나다.. 이게 바로 CPU의 성능 차이인가..

서브컴(노트북) IBM X20 CPU – 600, RAM – 384M 대략 87초 OTL  

메인컴(데스크탑) 듀얼코어 CPU - E6600, RAM – 2G 환상(?)적인 5초 ^^\

사용자 삽입 이미지사용자 삽입 이미지 CEXAM.exe

속도측정 실행파일

Tag |

이차원배열과 포인터 익히기

from Study/C언어 2007/03/22 17:06 view 23701

포인터는 C의 중간 고비쯤 된다. 이 능선만 잘 넘기면 C언어의 활용도가 한 200프로(?) 향상 되고 이해가 되지

않았던 부분들도 조금씩 풀려나가는 걸 알 수 있었다. 그러나 2차원 배열은 왠지 복잡한 부분이 있다.

포인터배열, 배열포인터, 2차원배열을 포인터로 주소 넘기기 이러한 것들이 나를 혼란스럽게 한다.

책을 읽어보니 이해가 팍팍 되었다. 그럼 책에 내용을 정리해본다.

    int imsi[3];

    int *imsip;

    imsip = imsi;

1차원 배열에서 배열과 포인터를 연결시키기 위한 간단한 대입 이다. 그렇다면 2차원 배열은 어떻게 하는걸까?

순간 **imsip 가 떠올라서 써본다면 "꽝" 이다. **imsip는 *ptr 의 주소값인 &ptr을 받는 변수이지 이차원
 
배열의 주소값을 받지 못한다.

*imsip2[3]; 과 (*imsip)[3]; 의 차이를 정확히 알고 있어야 함수로 주소값을 넘겨주는 작업을 할 때 고생을 줄일
 
수 있다. 이 둘의 차이는 과연 무엇일까?

imsi[0][0]

3

imsi[0][1]

5

imsi[1][0]

12

imsi[1][1]

54

imsi[2][0]

534

imsi[2][1]

923

   

imsip

 

 

temp[0]

 

temp[1]

 
   

    int imsi[3][2] = { {3,5} , {12,54}, {534,923} };

    int (*imsip)[2];

    int *temp[2];
 
 imsi[3][2]의 정의에 의해서 메모리는 24바이트가 1차원적으로 할당된다.

 (*imsip)[2]에 의하여 변수가 하나 할당된다. 이 변수의 이름은 imsip가 되고 2차원배열을 가리킬 수 있는
 
2차원 배열포인터 변수이다.

  *temp[2]는 포인터 변수가 두 개가 생성되고 메모리에도 당연히 두 변수에 대한 할당이 이루어 진다. 주소를

저장할 수 있는 공간이 두 개 생성 된다는 것이다.

쉽게 이야기하면 temp라는 것은 포인터를 저장 할 수 있는 영역이 두개 있는 것이다. 포인터변수가 2개 생기는 게

키포인트!!
이 정도면 포인터배열과 배열포인터의 차이를 어느 정도 이해할 듯 싶다. 특히 이것와 같이 꼭 알아야
 
할 것이 있는데 배열이 가리키는 주소의 범위이다. 이걸 모르고서는 포인터를 안다고 하면 안될 것이다.

imsip = imsi;         2차원배열포인터에 2차원배열의 주소를 대입 하고 있다.

imsip = &imsi[0][0];    2차원배열포인터에 1차원배열의 주소를 대입하고 있다.

temp[0] = imsi;        포인터변수에 2차원배열의 주소를 대입하고 있다.

temp[0] = imsi[0];    포인터변수에 1차원배열의 주소를 대입하고 있다.

2, 3번째는 틀린 문법이다. 위에 설명한 것과 같이 배열이 가리키는 주소의 범위에 따라서 포인터에 대입하느냐

못하는냐가 결정 된다는 것을 꼭 명심하자!

3.20(화) 함수의 설계 원칙 정리

from Study/C언어 2007/03/20 18:30 view 21894
함수를 작성하는 문법과 호출하는 방법, 인수를 받아들이고 리턴하는 방법을 익히는 것은 그다지 어렵지 않지만

정말로 함수답게 잘 나누고 디자인하는 것은 무척 어렵고 단기간에 체득되지 않는다. 함수는 프로그램을 구성하는

단위로서 잘 나누어 놓으면 프로그램의 구조가 탄탄해지고 확장하기도 쉽고 재사용성도 좋아진다.

함수디자인은 오로지 많은 분석과 실습만으로 얻어지는 경험이다. 꾸준한 연습만이 해결책이라 할수있다.


함수를 잘 만드는 기본적인 지침

1. 함수의 이름을 최대한 설명적으로 작성하여 이름만으로 무엇을 하는 함수 인지, 이왕이면 어떻게 쓰는 것인지도

알 수 있도록 한다. Score, Draw, Test 라는 이름보다도 GetScore, DrawScreen, TestGameEnd와 같이 기능을

명시해주는게 보기도 좋고 효율적이다. 자바를 이용하여 코딩해 보면 GET,SET 을 주로 함수앞에 써서 값을

얻거나 정해줄때를 명확히 표현해준다.


2. 두번이상 중복된 코드는 반드시 함수로 분리한다. 3번도 아니고 2번인 이유는 해당 동작을 수정해야 할때를

생각해 보면 된다. 만일 2번의 중복된 코드가 있는데 이 코드가 논리적으로 맞지 않다면 수정을 해줘야 할 것이다.

그러나 일일이 찾아가서 두개를 모두 고친다는 보장은 없다.(나같은 놈은...특히) 실수로 한 곳을 고치지 않으면

이것이 바로 버그의 원흉이 된다. 또한 두번 중복되는데 3번은 안되고 그이상은 안되겠는가 .. 미래를 위해서라도..


3. 반복되지 않더라도 한 단위로 볼 수 있는 작업은 함수로 만든다. 설사 이 함수를 딱 한번만 호출하고 다른

곳에서 호출할 확률이 희박하더라도 이렇게 하는 것이 좋다. 코딩을 하다보면 출력을 해야 하는 부분이 있고

입력을 해야 하는 부분이 있을 것이다. main 함수내에서 이를 전부 처리해도 문제는 없다.

하지만 어디가 출력부인지 입력부인지 가독성이게 표시를 할 수 있나가 문제이다. 물론 주석으로 일일히

표시해 줘도 되지만 가독성이 떨어 진다. 함수로 객체와 비스무리하게 나눠 주면 재사용도 좋고 보수하기도 좋고..


4. 함수는 한번에 하나의 작업만 해야 한다. 함수는 프로그램을 구성하는 부품이며 부품이란 전체를 구성하는

원자적인 단위이다. 함수 하나가 출력도 하고 입력도 받는다면 굳이 함수를 쓸 필요가 있을까 고장이 나도

구조가 간단한 부품에서 나야 하지 않을까 .. 함수도 마찬가지라 생각이 든다.


5. 입력과 출력이 직관적이고 명확해야 한다. 인수는 함수에게 주어지는 작업거리인데 함수가 하는 일에

꼭 필요한 정보만 최소한의 인수로 받아들여야 한다.

void CheckStr(char *str, int len);

과 같이 함수에 인수를 넘기고자 했을 때 굳이 문자열의 길이 len을 넘겨주지 않아도 strlen이라는 함수로

문자열의 길이를 판별할 수 있다는 것이다. 불필요한 입력이 생겨버린 셈이다.


6. 함수는 자체적으로 에러 처리를 해야 한다. 파일을 처리한다든지(fopen) 초를 계산한다든지(mktime)

할 때 에러가 발생하면 이들은 -1 , null 을 반환 해준다. 이러한 반환값을 함수내에서 에러 처리를 해줘서

어떤 프로젝트로 가져 가든 별도의 수정없이 재사용 가능한 부품이 되게 해줘야 한다. 만일 에러처리를 모두

메인 함수가 담당한다면 이 또한 함수의 사용의미를 잃어 버리는게 아닐까..
Tag |

선언과 정의는 비슷한 의미 같지만 함수나 변수등에서 사용되면 그 의미는 달라진다.

선언(Declartion) : 컴파일러에게 대상에 대한 정보를 알린다. 함수가 어떤 인수들을 전달받으며

어떤 타입을 리턴하는지를 알리는 원형 선언(프로토타입)이 대표적인 선언이다. 컴파일러에게 정보만 제공하는

것이므로 본체를 가지지 않으며 실제 코드를 생성하지도 않는다. 그래서 다음처럼 여러번 중복되어도 상관없다.

int MAX(int a, int b);
int MAX(int a, int b);

정의(Definition) : 대상에 대한 정보로부터 대상을 만든다. int i; 정의문에 의해 4바이트를 할당하며

int Max(int, int){ } 정의로부터 함수의 본체를 컴파일하여 코드를 생성한다. 정의는 변수의 타입, 함수의
 
인수목록을 컴파일러에게 알려 주기도 하므로 항상 선언을 겸한다.

선언 역할-알린다,     메모리-사용안함,  정보의 완전성-불완전해도 됨,      중복가능석-가능
정의 역할-생성한다,  메모리-할당,        정보의 완전성-항상 완전해야 됨,  중복가능성-불가능


하지만 실제로는 별 구분없이 사용되고 있다. 따라서 이러한 명칭적 정의가 있다는 것만 알고 있자..

#define A 1000     // 보통 디파인한다.정의한다로 하지만 메모리 할당을 받지 않으므로 선언이다.
int i;                   // 인트형을 선언한다 이런 말을 많이 쓰지만 메모리를 잡아 먹으므로 정의이다.
extern int j;        
// 외부 함수(모듈)에 정의된 int형을 선언하는거라고 해야 정확한 표현(?) 이다.

3.20(화) 매크로함수..

from Study/C언어 2007/03/20 15:02 view 21699

프로그램 내에서 자주 사용하는 상수나 반복문을 또 쓰기 싫거나 const상수를 만들고 싶을때

#define 문을 써서 표현 하는것이 좋다.

지금 제일 많이 쓰는 #define MAX 100 이런 문장은 프로그램의 최대값을 변경 하고 싶을 때

용이하게 할 수 있다. 매크로 라는 기능으로도 유용한데 그건 아랫글 참고..

2007/03/20 - [Study/CNX] - 3.20(화) 스피커음 내보기. // 이전글넣기 플러그인 너무 좋다. ^_^

매크로 함수는 함수를 흉내 내서 인수를 받아들이고 매크로 실행 후 계산 결과를 리턴한다.

하지만 이 리턴하는게 말이 리턴이지 문장의 치환에 불과하다.

함수처럼 연산을 실행한 후에 결과값을 반환하는게 아니라는 것이다. (맨날 헷갈려ㅠ_ㅠ.)

#define dubae(i) i+i
#define double(i) i*i
#define VALUE 100
#define VALUE2 VALUE+100

두개의 매크로문으로 정확히 알 수 있는데

-dubae(3); 이라 하면 함수로 착각하여 -6으로 생각이 든다. 하지만 이건 매크로다. 단순히 문장내에

정의한 매크로를 치환해주는 역할
만 해준다. 즉, -i+i; 라는 문장이 생기는 것이다.

double(3+3); 도 마찬가지로 6을 더한 후에 6*6이 될 거라고 생각하지만 아니다.

3+3 * 3+3 ; 으로 먼저 치환이 된다. 매크로문인 double을 먼저 치환해주고 연산을 해나가는 것이다.

전처리문을 보면 VALUE2가 200이라고 생각을 해버린다. 나 같은 사람은 이 순간 VALUE2는 200이라고

철썩 믿어버린다. 그럼 고정관념이 생겨서 오류가 나도 어디가 난지 죽어다 깨어나도 알지 못한다.

VALUE2*2+100 이런식으로 계산하면 500 하고 넘어가버리면 바로 버그가 탄생되는 것이다.

분명히 알아야 될 것은 매크로는 그저 문장내에 매크로에서 정의한 문장을 옮겨주는 역할만 한다.

그러므로 이 문장은 VALUE+100*2로 해석해야 정답이다. 결과값300이라는 값이 나오게 되는 것이다.

이를 방지하기 위해서 무조건 ( ) 로 묶어주는게 상책이다. -_-... 매크로로 들어간 문장이 최우선으로 계산하게

만들어 버려서 예상치 못한 실수를 원천방지 해버리자!!!

Tag |

3.20(화) 스피커음 내보기.

from Study/C언어 2007/03/20 14:00 view 21536

  왠지 예전 도스게임이 모락모락 떠오르는 음이다.. 띠~~ 띠..

#define BEEP(fre, time) Beep( (DWORD)(131*pow(1.06, fre)), (400*time));

Beep 함수를 매크로로 만들어 봤는데 -_-..  어설프네..

Beep(주파수 발생 , 발생시간) 으로 구성되어있는데 DWORD형으로 해줘야 경고 안먹는다...

131*pow(1.06, x) 이부분은 음계에 근접한 음이라고 한다. 도(0),레(2),미(4),파(5)....

Tag |

3.16(금) 우선순위에 대해서..

from Study/C언어 2007/03/16 10:27 view 25178
 

연산자

종류
결합성
우선순위
()  []  .  ->
최우선연산자
좌->우
높다
!  ~  -  *  &  sizeof  ++  -- cast
단항연산자
우->좌

* /  %  +  -
<<  >>
<  <=  >  >=  !=  ==
&&  ||  &  |  ^
이항연산자
좌->우

?:
삼항연산자
우->좌

=  +=  -=  *=  /=  %=  <<=  >>=  &=  |=  ^=
대입연산자
우->좌

,
순차연산자
좌->우
낮다

x = x + 1; x += 1; 의 줄여 쓰는게 가능하다. 하지만 =+ 는 되지 않는다.

이는 이항연산자가 대입연산자보다 우선순위가 빠르기 때문이다. 우선순위에 따라

좌->우로 컴파일하는데 +=를 만나면 x  와 1을 + 하는 이항연산을 하고 이값을 = 에

따라 lvalue에 대입시킨다.


int i = 5;
j = ++i + ++i;
// j 는 14가 된다.
i=5;
j = i++ + i++;
 // j는 10이 된다.

 ++i + ++i 는 언뜻보면 결과가 13 일 것 같다. 하지만 VC++ 컴파일러는 ++i 연산을 하게되면

스택내에 저장 되어 있는 값을 변경해준다. 그래서 6 + 7 일것 같지만 + 연산을 하면

변수 i에 저장된 값으로 이항연산을 하게 된다. 그래서 7 + 7 의 연산을 하는 애매모호한

값이 나오게 된다.

 i++ + i++ 는 5 + 6 으로 착각하기 쉽다. 하지만 후위증가는 문장의 끝인 ;(세미콜론)이

나와야 증가를 수행하게 된다. 즉 5 + 5로 연산을 수행 하고 문장이 끝나야 증가해서

i는 7이 된다.


int i = 5
printf("%d , %d", i , ++i);
//출력 6,6

 C는 함수의 인수를 뒤에서부터 순서대로 전달하도록 함수호출규약으로 정해 놓았다.


 if(y + 4 == 4 || ++y * 4 == 0)
 {
  printf("%d\n", y);
  printf("!\n");
 }

 그냥 혹시나 해서 이상한 코드를 만들어 보았다. if 문의 조건을 살펴볼때 + || * 이나

* || ( + ) 형식의 조건이 들어 있다면 왼쪽 먼저 일까 오른쪽이 먼저 일까 생각해 봤는데


이항연산자를 만나면 다른 이항연산자가 없다면 우선순위 비교 자체를 하지 않을 것이므

로....하여간 좀 바보같다..ㅠ_ㅠ

추가 : *&a[3] 을 우선순위대로 풀어보면 a 의 3번째 주소값의 indirection 을 행한다.

여기엔 * & [] 세가지의 연산자가 있는데 [] > & == * 이지만 & 와 *가 우선순위가 같다면 결합성이 우->좌

이기 때문에 & 먼저 연산되는 것을 알고 있자.
 

if(input == 5)
 num = 5;
else
 num = 0;


이런 패턴이 있다면 더 줄여 보고자 하는 욕심을 가지는 것이 좋다.

삼항조건연산자는 그저 이게 맞다면 앞에꺼 리턴 이런 생각을 가지기 쉬운데 더 응용해보자.

num = (input == 5) ? 5:0; 로 위에 코드를 대폭 줄일 수 있고 또,

(input == 5) ? ((output == 'a') ? 5 : 0) : 0; 같은 코드를 사용해서 if문을 중첩한 효과를 낼수

있다.

0과 다른수를 리턴하고자 할때 아주 간단하게 표현 가능한데

num = (input == 5) * 5;

생각해보면 간단하지만 참일때 1 , 거짓일때 0을 리턴한다는 것을 잘 활용한게 아닌가 싶다.


여러코드를 코드를 보다보면 이렇게도 응용을 할수 있구나라는 생각이든다.

과연 그 코드를 모르는 상태에서 이러한 코드를 생성할수 있을까...

/*10진수를 16진수로 출력하는 프로그램*/
#include <stdio.h>

void main()
{
 int input;
 int low, hi;

 while(1)
 {
  printf("0~255사이의 수를 입력하시오: ");
  scanf("%d", &input);
 
  hi = input >> 4;
  low = input & 0xf;
  printf("입력한 수의 16진 표기 = %c%c\n", hi+'0'+(hi > 9)*7, low+'0'+(low > 9)*7);

 }
}

10진에서 16진수를 구하기 위해 상위비트는 비트연산자를 통해 구하고

하위비트는 논리연산자로  마스크(?)를 만들어서 변수에 저장 시켰다.

그리고 나서 이를 16진수로 바꾸기 위해서 0의 아스키코드값 48과 더하면 9를 초과하는지의

여부를 알 수 있다. (hi >9)가 참이면 1 아니면 0을 리턴하는 것을 응용하여 9를 초과 했다면

아스키코드값이 대문자 A가 부터 표현하기 위해 7을 더해준다. (브라보!)