20140402 (main 인자전달, stream)

32일차

 

 

---------------------------

main 함 수 로   인 자 전 달

---------------------------

 

- int main( int iNum, char * Cmd[] )

main함수 인자에 위의 인자 처럼 넣어주면

iNum에는 받은 명령어 갯수,

Cmd에는 그 명령어들이 각각 저장되어 있다.

 

ex) gcc -o main main.c

iNum 에는 4,

Cmd[0] : gcc

Cmd[1] : -o

Cmd[2] : main

Cmd[3] : main.c

이렇게 저장됨.

 

인자의 이름은 프로그래머 재량이지만 보통

int main(int argc, char * argv[])

로 많이 쓴다.    argc ( ARGument Count )  argv ( ARGument Vector )

 

 

 

-------------------------

이중 포인터와 2차원 배열

-------------------------

- 이전에 한 번 했었는데 헷갈려서 다시 한 번 언급한다.

void test ( int ** ptr )

{ A }

int main()

{

    int arr[5][3];

    test( arr );

....

위의 소스처럼 썼었는데 warning!

이유는

char ** 와 char [][] 참조수준이 다름.

서로 데이터 타입이 달라서 그랬다.

2차배열은 이중포인터와 같은 것인지 착각했었다.

그래서 고치면

void test( int (* ptr)[3] ) { A }      or      void test( int ptr[][3] ) { A }

둘 다 동일한 선언이고 '배열 포인터'이다.

- int * whoA [4]    // 포인터 배열

- int (* whoA)[4]   // 배열을 가리키는 포인터

 

 

 

------------------

Stream 흐름

------------------

- ' >' Redirection. 출력 방향을 바꾼다.

- 표준입력 (키보드) : stdin, 0

- 표준출력 (모니터) : stdout, 1

- 표준에러 (모니터) : stderr, 2

ms-dos, linux에서는 0, 1, 2로 스트림으로 쓴다.

ex)

dir > a.txt              dir 내용을 a.txt에 넣음

dir 1> a.txt            dir 내용을 a.txt에 넣음

dir 2> a.txt            dir 에러 내용을 a.txt에 넣음

 

 

- ' >> ' 이것도 redirection인데 ' > ' 와 다른 점은

> : 새로 쓰기

>> : 추가로 쓰기

 

 

- fprintf( Stream, format, ...);

printf 함수와 다르게 stream을 정해서 출력할 수 있다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

설정

트랙백

댓글

20140401 (함수 포인터, void 포인터)

32일차

 

 

------------------------

함    수    포    인    터

------------------------

- 함수의 주소값을 저장하는 포인터.

먼저 이 포인터를 선언하기 위해서는 Data Type을 알아야한다.

Data Type은 Prototype(함수원형)에서 이름 부분만 (*)로 바꾸면 된다.

ex)

int main()                                main함수의 Data Type은

{ 명령어들 }                                   int (*)()  이다

void test()                               test함수의 Data Type은

{ 명령어들 }                                  void (*)()  이다

int printf(const char *, ... )        printf함수의 Data Type은

                                                   int (*)(const char *, ...)  이다

 

 

- 함수 포인터 선언 및 사용

#include <stdio.h>

void test()
{
  int (*fp)(const char *, ...) = printf;

  (*fp)("hello \n");  //  C++에서는 괄호를 넣지않으면 오류라고 한다.

  fp("hello \n");  //  C에서는 위와 밑에 둘 다 가능.

  
  return ;
}

int main()
{
  void (*fp)();
  
  fp = test;

  fp();

  return 0;
}

위와 같은 방법으로 사용한다.

 

 

 

 

-------------------------

void  형    포  인  터

-------------------------

- Data Type이 void * 형이다.

타입을 안가리고 그 주소값을 warning 없이 다 받을 수 있다.

그러나 값을 가리키는 *를 붙이면 자료형이 얼만큼 인지 모르니

사용할 수 없다.

캐스팅을 하면 사용가능.

ex)

#include <stdio.h>

int main()
{
  void *  vp;
  int  iNum = 500;
  char  cNum = 100;

  vp = &cNum;
  *((char *)vp) = 99;

  vp = &iNum;
  *((int *)vp) = 400;

  printf("%d \n", cNum);
  printf("%d \n", *((int *)vp) );
  printf("%d \n", iNum);  

  return 0;
}

위에 *((char *)vp) 식으로 char * 형이라고 캐스팅을 해서 사용.

 

 

 

아래 소스는 HexaView 코드인데

void * 가 사용되는 예를 보자.

#include <stdio.h>

void HexaView(void * vpData);

int main()
{
  int  iNum = 0x12345678;

  HexaView(&iNum);

  return 0;
}

void HexaView(void * vpData)
{  
  int iCnt;
  int iCnt2;
  
  printf("===============================================================================\n");
  printf("= ADDRESS                         HEXA                             ASCII      =\n");
  printf("=-----------------------------------------------------------------------------=\n");
  
  for(iCnt2=020>iCnt2; ++iCnt2)
  {

    //----------------------------//
    //---      Address Part Start      ---//

    printf("= %08X  "vpData);

    //---      Address Part End       ---//
    //----------------------------//
  


    //---    Hexa View Part Start    ---//

    for(iCnt=016>iCnt; ++iCnt)
    {
      if(7 == iCnt)
      {
        printf("%02X  "(*((unsigned char *)vpData)) );
      }
      else
      {
        printf("%02X "(*((unsigned char *)vpData)) );
      }
      vpData = ((unsigned char *)vpData) + 1;
    }
    //---    Hexa View Part End    ----//
    //----------------------------//



    //----------------------------//
    //---        ASCII Part Start       ---//

    vpData = ((unsigned char *)vpData) - iCnt;
    for(iCnt=016>iCnt; ++iCnt)
    {
      if(0x20 <= (*((unsigned char *)vpData)) && 0x80 > (*((unsigned char *)vpData)))
      {
        printf("%c"(*((unsigned char *)vpData)));
      }
      else
      {
        printf(".");
      }
      vpData = ((unsigned char *)vpData) + 1;
    }
    //---        ASCII Part End        ---//
    //----------------------------//
    printf(=\n");
  }  
  printf("===============================================================================\n");

  return ;
}

위에 연한 빨강색 바탕으로 강조된 곳들이

void *를 사용한 곳들이다.

main 함수에서 HexaView 함수를 호출 할 때에

void *를 사용하지 않았다면

HexaView( (unsigned char *)&iNum );

이런식으로 함수를 호출할 때마다 캐스팅을 해줘야 하기때문에

편의성의 위해서 작성할 때 좀 더 코드를 길게 적어주면 좋다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

설정

트랙백

댓글

20140331 (2차원 배열, 이중포인터, 배열포인터)

31일차

 

 

-------------------------

2차원 배열 메모리 구조

-------------------------

-

char Arr[3][3];

int * ip = &Arr[0][0];

2차원 배열이 실제 메모리에서는

 

이런 식으로 저장되어 있다.

그래서 아래와 같이 모두 같은 값을 가리킨다.

ip[3] == Arr[0][3] == Arr[1][0]

 

 

 

- 줄 이동 (주소값)

Arr + 0    =    &Arr[0] + 0    =    &Arr[0]

Arr + 1    =    &Arr[0] + 1    =    &Arr[1]

Arr + 2    =    &Arr[0] + 2    =    &Arr[2]

 

- 칸 이동 (주소값)

&Arr[0][0]    =    Arr[0] + 0    =    *(Arr + 0) + 0    =    *( &Arr[0] + 0 ) + 0

&Arr[0][1]    =    Arr[0] + 1    =    *(Arr + 0) + 1    =    *( &Arr[0] + 0 ) + 1

&Arr[0][2]    =    Arr[0] + 2    =    *(Arr + 0) + 2    =    *( &Arr[0] + 0 ) + 2

 

- 칸 이동 (값)

Arr[0][0]    =    *(Arr[0] + 0)    =    *( *(Arr + 0) + 0 )    =    *( *( &Arr[0] + 0 ) + 0 )

Arr[0][1]    =    *(Arr[0] + 1)    =    *( *(Arr + 0) + 1 )    =    *( *( &Arr[0] + 0 ) + 1 )

Arr[0][2]    =    *(Arr[0] + 2)    =    *( *(Arr + 0) + 2 )    =    *( *( &Arr[0] + 0 ) + 2 )

 

 

 

-----------------------

---배열에서 []숫자 생략

배열에서 []안에 숫자 생략이 가능한데 맨 왼쪽에 있는 숫자 하나만 가능하다.

다만 초기화시에만 가능.

int iaArr[ ] = {1, 2, 4, 5};

int iaArr[ ][2] = {  {1, 2}, {3, 4}  };

int iaArr[ ][2][2] = {  { {1, 2}, {3, 4} }, { {1, 2}, {3, 4} }  };

 

 

------------------

---포인터의 포인터

 

int iNum = 3;                                      iNum -> 3

                                                     &iNum -> iNum주소

int * ip = &iNum;                                     ip -> iNum주소

                                                         &ip -> ip주소

                                                          *ip -> 3

int ** ipp = &ip;                                    ipp -> ip주소

                                                        &ipp -> ipp주소

                                                        *ipp -> iNum주소

                                                       **ipp -> 3

 

 

--------------------------

---배열 포인터, 포인터 배열

int arr[3][4];                   //  배열

int * ptr2[4];                    // 포인터 배열 , 이 변수 크기 16byte

int (*ptr)[4];                    // 배열 포인터 (16byte씩 건너뛰는 포인터) , 이 변수 크기 4byte

 

int (* ptr)[4];    // 포인터방식 선언

int ptr[][4];      // 배열방식 선언

둘 다 4byte 크기의 포인터로 연산시 16byte씩 이동하는 같은 뜻이다.

 

ex) 예제 소스

#include <stdio.h>

void ShowArr2DStyle(int (*arr)[4]int column)
{
  int  i;
  int  j;

  for(i = 0; column > i; ++i)
  {
    for(j = 04 > j; ++j)
    {
      printf("%d "arr[i][j] );
    }
    printf("\n");
  }
  printf("\n");
  printf("arr : %d \n"sizeof(arr) );
}

int Sum2DArr(int arr[][4]int column)
{
  int  i;
  int  j;
  int  sum = 0;

  for(i = 0; column > i; ++i)
  {
    for(j = 04 > j; ++j)
    {
      sum = sum + arr[i][j];
    }
  }

  printf("arr : %d \n"sizeof(arr) );

  return sum;
}

int main()
{
  int  arr1[2][4= {1,2,3,4,5,6,7,8};
  int  arr2[3][4= {1,1,1,1,3,3,3,3,5,5,5,5};

  ShowArr2DStyle(arr1, sizeof(arr1)/sizeof(arr1[0]) );
  ShowArr2DStyle(arr2, sizeof(arr2)/sizeof(arr2[0]) );

  printf("arr1의 합 : %d \n", Sum2DArr(arr1, sizeof(arr1)/sizeof(arr1[0]) ) );
  printf("arr2의 합 : %d \n", Sum2DArr(arr2, sizeof(arr2)/sizeof(arr2[0]) ) );

  return 0;
}

위의 예제처럼 이 차원 배열로도 사용할 수 있다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

설정

트랙백

댓글

20140328 (const, 2차원배열)

30일차

 

 

-------------

const

-------------

 

---------------------------

--- 포인터에 const가 붙으면?

 

int a = 10;

a = 40;  //  가능

const int b = 20;

b = 10;  // 불가능

int * c = &a;

*c = 20;  //  가능

c = &b;  //  불가능

cont int * d = &a;

*d = 30;  // 불가능

d = &b  //  가능

int * const e = &a;

*e = 40;  // 가능

e = &b  // 불가능

const int * const f = &a;

*f = 11;  //  불가능

f = &b;  //  불가능

 

 

 

----------------------------

2      차      원      배      열

----------------------------

 

- 전에 Arr[5] 이게 1차원 배열이라면

int iaArr[3][4]; 2차원 배열이라 부른다

 

--- 배열과 포인터의 관계

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

설정

트랙백

댓글

20140327 (포인터, 배열, call by address, call by value)

29일차

 

 

--------------------------------------

포    인    터      ,     배    열

---------------------------------------

 

---------------------------

--- 포인터 사용시 주의할 점.

 

int  iNum = 10;
double *  dpNum = &iNum;  // Warning!

double  dNum = 10.5;
int *  ipNum = &dNum;  
// Warning!

같은 포인터 타입이기에 Error는 안나서 실행은 되지만

다른 자료형(실수, 정수)이므로 Warning입니다.

상황에 따라 어떤 오류가 발생할지는 모르니 잘못된 것입니다.

 

int *  ipNum;

*ipNum = 1000// Warning!

ipNum에 주소값도 정해져 있지 않은데 그 주소값에 저장된 값을 바꾸면

어떤 문제가 발생할지 모르니 이것도 잘못된 것. warning, 런타임 에러

 

int *  ipNum = 1000;

*ipNum = 10;

ipNum에 1000번지를 넣었는데 1000번지가 어딘지 알고 넣었는지?

또 그 값을 변경도 시켰으니 Warning, 런타임 에러

 

int *  ipNum1 = 0;
int *  ipNum2 = NULL;

포인터는 NULL (0) 으로 초기화 값으로 주는게 좋다고 합니다.

그래서 아래 코드를 시험해 보니

#include <stdio.h>

int main()
{
  int *  ipNum = NULL;

  *ipNum = 10;

  return 0;
}

초기화 값을 뭘 주든 제대로된 주소가 아니면 런타임 오류가 납니다.

 

 

 

----------------

--- 포인터 연산

 

출력 화면

포인터를 연산할 경우 그 자료형 만큼 주소값이 움직입니다.

char * cpNum = 1000;

cpNum = cpNum + 2;    =>   1000 + 2 * 1byte   =>   1002   (2byte움직임)

int * ipNum = 2000;

ipNum = ipNum + 2;    =>    2000 + 2 * 4byte   =>   2008   (8byte움직임)

double * dpNum = 3000;

dpNum = dpNum + 2;    =>   3000 + 2 * 8byte    =>    3016   (16byte움직임)

 

 

 

-----------------------

--- *(ipNum+0) == arr[0]

 

배열도 포인터도 주소값을 저장하고 있어서

사용법도 비슷합니다. 그래서

*(ipNum+0) == arr[0]    <-- 이 등식이 성립합니다.

 

ex)

int main()
{
  int  arr[3= {112233};
  int *  ptr = arr;

  printf("arr[0] : %d \n", arr[0]);
  printf("arr[1] : %d \n", arr[1]);
  printf("arr[2] : %d \n", arr[2]);

  printf("ptr[0] : %d \n", ptr[0]);
  printf("ptr[1] : %d \n", ptr[1]);
  printf("ptr[2] : %d \n", ptr[2]);

  printf("*(arr + 0) : %d \n", *(arr + 0) );
  printf("*(arr + 1) : %d \n", *(arr + 1) );
  printf("*(arr + 2) : %d \n", *(arr + 2) );

  printf("*(ptr + 0) : %d \n", *(ptr + 0) );
  printf("*(ptr + 1) : %d \n", *(ptr + 1) );
  printf("*(ptr + 2) : %d \n", *(ptr + 2) );

  return 0;
}

출력 화면

*(ipNum+0) == arr[0]    <-- 이 등식이 성립합니다.

 

 

 

 

------------------------------------

--- char str1[] = "My String";
     char * str2 = "Your String";  장단점

 

여기서 str2 포인터 변수는 스택에 변수를 가리키는게 아니라

소스에 저장된 Your String을 가리킨다는 것을 명심!

 

배열

메모리 더 먹음, 수정 가능.

포인터

메모리 4byte만 먹음, 수정 불가.

 

 

 

 

 

--- 포인트 배열

포인트 타입의 배열이다.

ex)

arr 배열에 0, 1, 2 주소에 각각 주소값이 저장되어 있습니다.

printf("%d \n", *arr[0] );

->  printf("%d \n", *0x1000 );

->  printf("%d \n", 10 );

이런 과정을 거치겠죠....

 

 

 

 

--- 함수의 인자로 배열로 받을 수 없다.

그래서 포인터로 받는다.

ex)

 

 

 

 

 

--- Call by Address, Call by Value

함수에 인수를 다룰 때 쓰는 방법.

-   Call by Address     : 주소를 넘겨 호출.

-   Call by Value         : 을 넘겨 호출.

ex) 예제 소스

#include <stdio.h>

void CallByValue(intint);
void CallByAddress(int *, int *);

int main()
{
  int  iNum1 = 10;
  int  iNum2 = 100;

  CallByValue(iNum1, iNum2);  //  Call By Value
  printf("CallByValue() -> %d %d \n", iNum1, iNum2);

  CallByAddress(&iNum1, &iNum2);  //  Call By Address
  printf("CallByAddress() -> %d %d \n", iNum1, iNum2);

  return 0;
}

void CallByValue(int n1, int n2)
{
  int  iTemp;

  iTemp = n1;
  n1 = n2;
  n2 = iTemp;
}

void CallByAddress(int * n1, int * n2)
{
  int  iTemp;

  iTemp = *n1;
  *n1 = *n2;
  *n2 = iTemp;
}

출력 화면

 

CallByValue 는 말 그대로 main에 있는 변수의

가져와 쓰기 때문에 CallByValue 에서 무슨 짓을 해도

main의 변수에 접근할 수 없다.

 

CallByAddress 는 말 그대로 main에 있는 변수의 주소값

가져와 쓰기 때문에 CallByAddress 에서 무슨 짓이든

main의 변수에 접근할 수 있다.

 

 

 

설정

트랙백

댓글

20140326 (배열)

28일차

 

----------------

Array ( 배 열 )

----------------

- 배열 초기화

char caString[256] = "";

char caString[256] = {0};

둘다 0(NULL)로 초기화 된다.

 

 

- Call by Address, Call by Value

이것은 함수에 인수를 다룰 때 쓰는 방법이다.

Call by Address : 주소를 넘겨 호출.

Call by Value : 값을 넘겨 호출.

ex) 예제 소스

#include <stdio.h>

void CallByValue(intint);
void CallByAddress(int *, int *);

int main()
{
  int  iNum1 = 10;
  int  iNum2 = 100;

  CallByValue(iNum1, iNum2);  //  Call By Value
  printf("CallByValue() -> %d %d \n", iNum1, iNum2);

  CallByAddress(&iNum1, &iNum2);  //  Call By Address
  printf("CallByAddress() -> %d %d \n", iNum1, iNum2);

  return 0;
}

void CallByValue(int n1, int n2)
{
  int  iTemp;

  iTemp = n1;
  n1 = n2;
  n2 = iTemp;
}

void CallByAddress(int * n1, int * n2)
{
  int  iTemp;

  iTemp = *n1;
  *n1 = *n2;
  *n2 = iTemp;
}

출력 화면

CallByValue 는 말 그대로 main에 있는 변수의 값

가져와 쓰기 때문에 CallByValue 에서 무슨 짓을 해도

main의 변수에 접근할 수 없다.

CallByAddress 는 말 그대로 main에 있는 변수의 주소값

가져와 쓰기 때문에 CallByAddress 에서 무슨 짓이든

main의 변수에 접근할 수 있다.

 

 

- 여기서 잠깐 무시무시한 scanf()의 무서움을 보자.

ex) 예제 소스

#include <stdio.h>

int main()
{
  char  cStr[8];

  printf("문자열 입력 : ");
  scanf("%s", cStr);

  printf("입력 받은 문자열 : %s \n", cStr);

  return 0;
}

출력 화면

 

여기까지는 별 문제 없어 보인다.

하지만

입력한 문자가 9개를 넘어가면서 오류가 발생한다.

위에서 cStr[8] 크기를 8byte로만 지정했기 때문에

9byte (9글자)를 받았을때에 오류가 난다.

 

지금까지 scanf("%d", &iNum); 이렇게 항상 변수 앞에 &를 붙였는데

포인터를 배우면서 보았듯이 주소값을 넘겨주기 때문에

scanf() 함수에서 main함수의 변수를 만질 수 있었다.

그래서 그 이상의 값이 들어왔을때 오류가 나는 것을 볼 수 있다.

상위 버전 컴파일러로 컴파일시 scanf 함수 사용만으로도 warning을 출력한다고 한다.

그러니 지금은 scanf() 함수를 쓰지만 나중에 안정성을 위해

다른 함수를 사용해야할 것이다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

설정

트랙백

댓글

20140325 (배열, 문자열)

27일차

 

 

--------------

Array ( 배열 )

--------------

어제에 이어 계속 배열을 배웠다.

바로 예제 소스를 보자.

ex) 예제 소스

#include <stdio.h>

int main()
{
  int  arr1[5= {1,2,3,4,5};
  int  arr2[] = {1,2,3,4,5,6,7};
  int  arr3[15= {1,2};
  int  ar1Len;
  int  ar2Len;
  int  ar3Len;
  int  iCnt;

  printf("배열 arr1의 크기 : %d \n"sizeof(arr1) );
  printf("배열 arr2의 크기 : %d \n"sizeof(arr2) );
  printf("배열 arr3의 크기 : %d \n"sizeof(arr3) );

  ar1Len = sizeof(arr1) / sizeof(int);
  ar2Len = sizeof(arr2) / sizeof(int);
  ar3Len = sizeof(arr3) / sizeof(int);

  for(iCnt = 0; ar1Len > iCnt; ++iCnt)
  {
    printf("%d ", arr1[iCnt]);
  }
  printf("\n");

  for(iCnt = 0; ar2Len > iCnt; ++iCnt)
  {
    printf("%d ", arr2[iCnt]);
  }
  printf("\n");

  for(iCnt = 0; ar3Len > iCnt; ++iCnt)
  {
    printf("%d ", arr3[iCnt]);
  }
  printf("\n");

  return 0;
}

출력 화면 

위에서 보았듯이

int arr1[5] = {1,2,3,4,5}; 선언시 {} 를 사용해서 초기화가 가능하고

int arr2[] = {1,2,3,4,5,6,7}; 변수 뒤에 []만 붙이면 몇 개나 들어가는지 자동으로 샌다.

int arr3[15] = {1,2}; 첫번째와 두번째만 넣고 나머지는 0으로 초기화.

 

- 배열은 잘 사용해야 하는게 다른 메모리를 침범이 가능하므로

잘 확인하고 사용해야 한다.

 

- cp == cStr ?????

#include <stdio.h>

int main()
{
  char *  cStr = "Good morning!";
  char *  cp1;
  char *  cp2;

  cp1 = cStr;

  printf("%c \n", *cp1);

  printf("%s \n", cp1);
  printf("%s \n", cStr);

//  ++cp1;
  cp1 = cp1 + 5;

  printf("%s \n", cp1);
  printf("%s \n", cStr+5);

  cp2 = cStr;

  printf("%c \n", cStr[0]);
  printf("%c \n", cp2[0]);
  printf("%c \n", *(cp2+0) );
  printf("%c \n", *(cStr+0) );

  return 0;
}

출력 화면

 

이것을 통하여

cp == cStr

똑같이 주소값을 저장하고 있다.

조금 다른데

cp의 경우 주소값이 가리키는 값을 수정할 수 있는 반면

cStr의 경우 수정이 불가능하다.

const char * const cp == cStr 이게 정확한 똑같은 Data type이다.

 

 

 - 문자열, 문자배열

무조건 문자가 모여있다고 문자열이라고 안한다.

마지막에 NULL이 없으면 printf함수 사용 시 NULL 만나기 전까지

출력하므로 계속 출력을 하게되므로 의도치 않은 상황이 발생할 수 있으니

위험하다.

 

 

- 주소상수

#include <stdio.h>

int main()
{
  char  str[] = "Hello world!\n";
  char *  cp = "Hello world!\n";

  printf("str : 0x%08X \n", str);
  printf("&cp : 0x%08X \n"&cp);
  printf("cp : 0x%08X \n", cp);
  printf("main : 0x%08X \n", main);

  str[5= '!';
  cp[5= '@';

  printf(str);
  printf(cp);
  printf("Hello world!\n");

  return 0;
}

출력 화면

 

 

- 그리고 위에서 보면

cp[5] = '!'; 를 자세히 보면 cp포인터 변수가 가리키는 곳은 CODE 영역의 상수값이다.

윈도우에서는 실행이 되지만 리눅스에서는

이렇게 오류가 난다.

Windows 에서는 RO영역의 수정을 어느정도 허용하지만 (위험함)

linux 에서는 RO영역의 수정을 금지한다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

설정

트랙백

댓글

20140324 (전역변수, 메모리 구조, 재귀함수)

26일차

 

--------------------

전  역  변  수

--------------------

- 지역변수와 다르게 어디서든 읽기 쓰기가 가능한 변수.

- 어떤 함수에서 쓰기 가능하기 위해 컴파일러가 제일 먼저 읽어야 하니

맨(include 밑에) 위에 선언한다.

- 기본적으로 0으로 초기화 돼있음.

- 변수가 만들어 지는 때

    지역변수 : Run Time

    전역변수 : Compile Time

 

- 지역변수는 Load 하면서 Mem에 할당됨.

- 전역변수는 컴파일당시에 생성되기 때문에 실행파일에 할당되어 있음.

 

ex) 예제 소스

#include <stdio.h>

void Add(int iVal);
int iNum;
int iNum1 = 10;
int iNum2;
int iNum3 = 20;
int iNum4;
int iNum5 = 30;
int iNum6;
int iNum7 = 40;

int main()
{
  printf("iNum : 0x%08X \n"&iNum);
  printf("iNum1 : 0x%08X \n"&iNum1);
  printf("iNum2 : 0x%08X \n"&iNum2);
  printf("iNum3 : 0x%08X \n"&iNum3);
  printf("iNum4 : 0x%08X \n"&iNum4);
  printf("iNum5 : 0x%08X \n"&iNum5);
  printf("iNum6 : 0x%08X \n"&iNum6);
  printf("iNum7 : 0x%08X \n"&iNum7);
  printf("Add : 0x%08X \n", Add);
  printf("main : 0x%08X \n", main);
  printf("printf : 0x%08X \n", printf);

  printf("iNum : %d \n", iNum);
  Add(3);
  printf("iNum : %d \n", iNum);
  ++iNum;
  printf("iNum : %d \n", iNum);

  printf("문자열 주소 : 0x%08X \n""Welcome");
  return 0;
}

void Add(int iVal)
{
  if(0 == iNum)
  {
    printf("iVal : 0x%08X \n"&iVal);
  }
  
  iNum = iNum + iVal;
}

출력 화면

위에서 보았듯이 전역변수로 선언되 있는 변수는

main에서도 Add에서도 접근이 가능하다.

자세히 볼게 전역변수들을 관찰해 보면

초기화된 변수와 초기화 안된 변수의 주소값이 몰려 있는게

다르다.

초기화된 변수는 함수들의 주소값에 더 가깝다.

또 상수의 주소도도 초기화된 변수들의 주소와 가깝다.

 

 

 

 

 

-----------------

재  귀  함  수

-----------------

- 자신을 다시 부르는 함수

- 사용을 비추천

 

ex) 예제 소스

#include <stdio.h>

void Recursive(int iNum);

int main()
{
  Recursive(3);

  return 0;
}

void Recursive(int iNum)
{
  if(0 >= iNum)
  {
    return;
  }
  printf("Recursive call!! %d \n", iNum);

  Recursive(iNum - 1);
}

 출력 화면

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

설정

트랙백

댓글

20140321 (함수, 지역변수)

25일차

 

--------------------

Function ( 함  수 )

--------------------

- 어제 보았듯이 어떤 기능을 하는 역할.

- int Add(); 이런 형태는 함수의 원형( prototype, interface ) 라고 한다.

- int Add() { A } 이런 형태는 함수의 정의이다.

뭐 설명은 어려운데 예제를 보면 쉽다.

예제를 보자.

#include <stdio.h>

int Add(int iNum1, int iNum2);  // 함수 선언, 원형

/*
int Add(int iNum1, int iNum2)    // 이렇게 main 위에 와도 괜찮다.
{        // 선언, 정의? 다 같이 한다?
  int iResult = iNum1 + iNum2;

  return iResult;
}
*/


int main()
{
  int  iNum;

  iNum = Add(1020);
  printf("%d \n", iNum);

  return 0;
}

int Add(int iNum1, int iNum2)    // 함수 정의
{
  int iResult = iNum1 + iNum2;

  return iResult;
}

프로그램 진행 순서가

이렇게 흘러간다.

10, 20 이란 인수를 받아서 Add 함수에서 그 값만 받는다.

받은 값을 iResult에 넣어서 반환한다.

 


 

위에서 본 바와 같이

어떤 일정한 반복된 코드나,

역할을 나눌때

함수를 만들어 사용한다.

 

 

 

여기서 잠깐,

printf, scanf 등등 가져다 쓰는 함수들을 모아서

라이브러리 라고 부르고 컴파일 과정 중 링크때 연결된다.

 

우리가 만든 함수는 전처리 과정 중에 불러온다.

 

 

 

 

 

 

-------------

지역변수

-------------

- { 에서 } 까지가 지역변수의 수명.

ex) 예제 소스

#include <stdio.h>

main()
{
  int iNum;    // main 의 지역변수 iNum

  iNum = 0;

  if(0 == iNum)
  {
    int iNum = 1;    // if 의 지역변수 iNum
  }                      // if 의 지역변수 iNum 소멸

  printf("iNum : %d \n", iNum);    // main 의 지역변수 iNum

  while(2 > iNum)    // main 의 지역변수  iNum
  {
    int iTemp = 0;    // while 의 지역변수 iTemp 생성
                          // while문이 반복할 때마다 iTemp 생성, 소멸 반복
    ++iTemp;
    ++iNum;
    
    printf("iTemp : %d \n", iTemp);
    printf("iNum : %d \n", iNum);
  }                      // while문 지역이 끝나면서 iTemp도 소멸

  {
    int iNum = 0;    // 이름없는 지역의 지역변수 iNum

    
    iNum = 1000;
  }                     // 지역이 끝나면서 iNum도 소멸

  printf("iNum : %d \n", iNum);    // main 의 지역변수 iNum

  return 0;
}

출력 화면

위에서 보았듯이 지역변수끼리 이름만 같았지

서로 다른 주소를 사용하기 때문에 서로 영향을 끼칠 수 없다.

변수 이름이 같을 경우

더 작은 지역의 변수의 이름을 사용하고

없을 경우 한층 위 지역의 변수를 사용한다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

설정

트랙백

댓글

20140320 (포인터, Hexa View, 함수)

24일차

 

----------------

포   인   터

----------------

 

- float형이 메모리에 IEEE 754방식으로 저장되어 있는지 확인해 보자

소스 코드

#include <stdio.h>

int main()
{
  float    fNum = 3.25;
  unsigned char *  ucpData = (unsigned char *)&fNum;

  printf("%02X \n", *ucpData);
  printf("%02X \n", *(ucpData+1));
  printf("%02X \n", *(ucpData+2));
  printf("%02X \n", *(ucpData+3));
  printf("%.3f \n", fNum);

  printf("\n");

  *(ucpData+0= 0x00;
  *(ucpData+1= 0x00;
  *(ucpData+2= 0xB9;
  *(ucpData+3= 0x41;

  printf("%02X \n", *ucpData);
  printf("%02X \n", *(ucpData+1));
  printf("%02X \n", *(ucpData+2));
  printf("%02X \n", *(ucpData+3));
  printf("%.3f \n", fNum);

  return 0;
}

출력 화면 

역시 어제 배운대로 거꾸로 저장되어 있다.

그런데 알아보기 힘들다. 왜? IEEE 754 방식으로 저장되어 있기 때문에

좀 해석?이 필요하다.

 

 

 

 

----------------

Hexa View

----------------

- 메모리영역을 16진수로 보는 것이다.

 

Visual Studio 2010 에서 열어본 것,

 

리눅스에서 출력해 본 화면이다.

소스코드를 보자면

#include<stdio.h>

int
 main()
{  
  int iNum = 0xABCDEF98;
  int iCnt;
  int iCnt2;
  unsigned char * ucpData = (unsigned char *)&iNum;
  
  printf("===============================================================================\n");
  printf("= ADDRESS                         HEXA                             ASCII      =\n");
  printf("=-----------------------------------------------------------------------------=\n");
  
  for(iCnt2=020>iCnt2; ++iCnt2)
  {
    //----------------------------//
    //---  Address Part Start  ---//
    printf("= %08X  ", ucpData);
    //---  Address Part End    ---//
    //----------------------------//
  
    //--- Hexa View Part Start ---//
    for(iCnt=016>iCnt; ++iCnt)
    {
      if(7 == iCnt)
      {
        printf("%02X  ",*ucpData);
      }
      else
      {
        printf("%02X ",*ucpData);
      }
      ++ucpData;
    }
    //--- Hexa View Part End  ----//
    //----------------------------//

    //----------------------------//
    //---   ASCII Part Start   ---//
    ucpData = ucpData - iCnt;
    for(iCnt=016>iCnt; ++iCnt)
    {
      if(0x20 <= *ucpData && 0x80 > *ucpData)
      {
        printf("%c",*ucpData);
      }
      else
      {
        printf(".");
      }
      ++ucpData;
    }
    //---   ASCII Part End     ---//
    //----------------------------//
    printf(=\n");
  }  
  printf("===============================================================================\n");

  return 0;
}

소스 중에 아스키코드를 출력하는 부분이 있는데

여기서 알아야 할 것이 아스키 코드 31번 밑으로는 제어 문자,

128번 이후는 확장아스키코드로 제대로된 문자가 나오지 않는다.

따라서 32~127번 까지만 아스키코드로 출력 가능하다.

 

 

---------------------

Function (함수)

---------------------

- 어떤 기능을 하는 것을 Function(함수)라고 하는데

이전까지 사용하던 main, printf, scanf도 다 함수이다.

이 함수들도 Symbol Table이 만들어진다.

그러면 함수들도 주소 값이 있다는 말이다.

여기서 잠깐

프로그램이 메모리를 이런식으로 사용하는데

 

자세한건 차차 배운다.

 

또 함수는 리턴 값이 있는데 이것은 컴파일러마다

다르기 때문에 그에 맞는 매뉴얼을 따로 봐야한다.

Microsoft Visual Studio는 MSDN이라 불리는 매뉴얼이 있고

리눅스 에서는 man 이라는 매뉴얼이 있는데

man "확인할 명령어 or 함수"

(printf 같은 경우 리눅스에도 prinft라는 명령어가 있기때문에

첫 페이지에 명령어가 나오는데 다른 페이지를 보려면

man 숫자 함수  <-- 이런식으로 써야된다

man 3 printf    printf같은 경우 이렇게 본다)

 

 

- 함수의 구조

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

설정

트랙백

댓글