2011년7월4일_배열의 주소와 값의 표기법, 함수포인터배열, TCP/IP소켓통신흐름, 접속한 클라이언트의 IP주소알아내기, 함수포인터의 반환형찾기

 

 

배열의 주소와 값의 표기법

 

● 1차원 배열의 각 원소의 주소와 값

int A[3] = {1, 2, 3};
배열식표기 &A[0] &A[1] &A[2] A[0] A[1] A[2]
포인터식 A + 0 A + 1 A + 2 *(A + 0) *(A + 1) *(A + 2)


1차원 배열의 주소와 값의 표기는 상기의 표와 같으며 배열이름 A자체는 A배열 전체의 주소를 갖는다.
따라서 &A는 배열A의 시작주소이고, &A + 1은 배열의 다음 주소인 시작주소 + 12Bytes가 된다.

 

 

● 2차원 배열의 각 행과 열의 주소를 알아보자.

   1:  // 2차원배열 각 원소의 주소값출력하여 배열개념을 이해하자.
   2:  #include <stdio.h>
   3:   
   4:  int main()
   5:  {
   6:      int i, j, iCnt = 0;
   7:      int array[3][4] =  {{15, 23, 45, 56},
   8:                          {34, 52, 76, 23},
   9:                          {43, 62, 91, 84}};
  10:   
  11:      //배열의 주소
  12:      printf("[%2d] %08X array의 주소 \n", iCnt++, array);                        //배열의 첫 행의 시작주소?
  13:      printf("[%2d] %08X array[0][0]의 주소\n", iCnt++, &array[0][0]);            //첫 원소의 주소
  14:      printf("[%2d] %08X array[0]의 주소\n", iCnt++, array[0]);                    //첫 행의 주소
  15:      printf("[%2d] %08X &array[0]의 주소\n\n", iCnt++, &array[0]);                //첫 행의 주소 (행전체)
  16:      //배열의 행과 열의 주소값
  17:      printf("[%2d] %08X array + 1의 주소\n", iCnt++, array + 1);                //행 이동
  18:      printf("[%2d] %08X &array[0][0] + 1의 주소\n", iCnt++, &array[0][0] + 1);    //열 이동
  19:      printf("[%2d] %08X array[0] + 1의 주소\n", iCnt++, array[0] + 1);            //열 이동
  20:      printf("[%2d] %08X &array[0] + 1의 주소\n\n", iCnt++, &array[0] + 1);        //행 이동
  21:      //포인터식표현
  22:      printf("[%2d] %08X array  \n", iCnt++, array);            //배열의 첫 행의 시작주소        
  23:      printf("[%2d] %08X *array \n", iCnt++, *array);        //배열의 첫 행의 첫 원소의 주소
  24:      printf("[%2d] %08X *array+1 \n", iCnt++, *array + 1);    //열이동 (1열)
  25:      printf("[%2d] %d **array \n", iCnt++, **array);        //첫 원소의 값.
  26:      printf("[%2d] %08X &array+1 \n", iCnt++, &array + 1);    //배열전체의 다음 주소 (배열을 벗어남)
  27:      printf("[%2d] %08X &iCnt \n", iCnt++, &iCnt);                //iCnt의 주소와 같게됨.
  28:      
  29:      return 0;
  30:  }

 


<실행결과>image

이름 설명
0 array 0행의 시작주소(0열)
1 &array[0][0] 0행 0열의 주소
2 array[0] 0행 0열의 주소
3 &array[0] 0행의 시작주소
4 array + 1 0행의 다음 행인 1행의 시작주소
5 &array[0][0] + 1 0행 0열에서 다음 원소인 0행 1열의 주소
6 array[0] + 1 0행 0열에서 다음 원소인 0행 1열의 주소
7 &array[0] + 1 0행의 다음 행인 1행의 시작주소
8 array 0행의 시작주소(0열)
9 *array 0행의 첫 원소인 0열의 주소
10 *array + 1 0행 0열에서 다음 원소인 0행 1열의 주소
11 **array 배열의 첫 원소의 값. (시작주소)
12 &array + 1 시작주소에 배열크기를 더한 주소
13 &iCnt 배열 전에 선언된 변수의 주소


0, 3, 4, 7, 8회에 출력된 주소값은 모두 행단위 주소를 나타내고,
1, 2, 5, 6, 9, 10회에 출력된 주소값은 모두 항의 주소로 열단위 주소를 나태낸다.
11회의 **array는 어렵게 보이나 바꾸어 보면 *(*(array + 0) + 0)으로 행과 열이 생략된 배열의 포인터식 표기법이다.

12회와 13회의 결과를 보면 배열을 사용할 때 주의를 해야할 점이 보인다.
배열전체의 주소 &array에 1을 더한 값은 배열 다음에 위치하는 변수의 주소이다.
지역변수는 stack에 저장되므로 array배열 보다 전에 선언된 iCnt가 위치하고 있다는 것을 13회의 주소출력 결과를 보면 알 수 있다.

 

● *(별표) 하나는 [ ](대괄호)와 ( )(둥근괄호)로 대치될 수 있다.

image 

 

● 형식인수로 다차원배열이 사용되는 경우 선언할 떄와 같이 첫 인자를 생략할 수 있다.

1차원배열 – A[]
2차원배열 – A[][3]
3차원배열 – A[][3][3]

 

 


함수포인터배열


● 함수포인터배열

image  

함수포인터는 함수의 실행코드의 시작주소를 가지고 있으며, 함수포인터를 이용하여 함수를 호출할 수 있다.
함수포인터배열은 함수포인터를 관리하기 쉽게 배열형태로 만든 것으로 응용 프로그램에서 많이 사용된다고 하는데 잘 모르겠음.



TCP/IP 소켓통신흐름


● bind( )


image 

클라이언트와 서버는 서버의 주소와 포트번호에서 접속된다. 이것이 가능하려면 서버는 먼저 bind( )를 통해 해당 주소와 포트를 소켓에 연결해야 한다. 클라이언트는 connet( )로 접속 지점인 서버의 주소를 제공해야 하고, 서버는 bind( )로 서버 자신의 주소를 명시해야함.

   1:  //성공시 0, 실패시 -1반환
   2:  //소켓디스크립터, 범용주소 구조체의 주소, 그 구조체의 크기를 인자로 넘겨줌.
   3:  int bind(int socket, struct sockaddr *localAddress, socklen_t addressSize);

 

● listen( )

//생략…

● accept( )

//시간관계상ㅠㅠ

● connet( )

//마찬가지 ㅠㅠ

 

● 전체적인 흐름

<서버>
1. 클라이언트의 연결을 받기 위한 서버소켓 생성.  socket( )
2. IP와 Port번호를 구조체에 설정 htonl( )
3. 서버소켓에 IP와 Port번호 구조체 값 할당. bind( )
4. 서버소켓 대기모드, 클라이언트 최대접속 갯수 설정. listen( )
5. 연결요청한 클라이언트오 통신할 송수신용 소켓 생성. (실제 통신용)  accept( )

5번만 blocking이 됨.

<클라이언트>
1. 서버접속을 위한 소켓 생성. socket( )
2. 서버IP와 Port번호 설정 –> 구조체. inet_addr( )
3. 소켓을 가지고 목적지IP, Port번호에 연결요청 connect( )

서버 5단계, 클라이언트 3단계까지 진행 후에 데이터 송수신할 수 있음.

 

 

접속한 클라이언트의 IP와 Port번호 알아내기



   1:      //연결 요청 수락
   2:      clnt_addr_size = sizeof(clnt_addr);
   3:      clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &clnt_addr_size);
   4:      
   5:      if(-1 == clnt_sock)
   6:      {
   7:          error_handling("accept() error");
   8:      }
   9:   
  10:      printf("클라이언트 주소 %s\n", inet_ntoa(clnt_addr.sin_addr));
  11:      printf("클라이언트 포트 %d\n", ntohs(clnt_addr.sin_port));


32비트 주소를 dotted decimal로 바꾸기 위해 inet_ntoa( )를 사용했고,
big endian으로 된 포트번호 16비트를 little endian으로 바꾸기 위해 ntohs( )를 사용했다.

포트번호가 계속 바뀌고 9190(바인드하기전 포트번호)와 다른데 이유는 모르겠다.
선생님의 말씀을 잘 경청하지 않아 놓친 것 같다.




숙제


● printf( )를 반환하는 함수의 원형을 찾기

   1:  //함수주소의 반환형 찾기
   2:  #include <stdio.h>
   3:   
   4:  int (*test(void))(const char *,...);
   5:   
   6:  int main()
   7:  {
   8:      printf("%08X\n", printf);
   9:      printf("%08X\n", test());
  10:   
  11:      return 0;
  12:  }
  13:   
  14:  int (*test(void))(const char *,...)
  15:  {
  16:      return printf;
  17:  }
  18:   

 

image image

리눅스에선 잘 되나…윈도우 CL컴파일러는 경고를 출력한다.
정리해보면..

   1:  //반환할 함수의 반환형,
   2:  //호출함수의 이름(인수의 자료형), 
   3:  //반환할 함수의 (인수의 자료형) 순서로 적으면 된다.
   4:     int (*test(void))(const char *,...);


어짜피 test( )는 함수포인터를 반환하니 함수포인터자체가 반환값이고,
반환할 함수의 반환형을 먼저 적어주고,
test( )의 인수의 자료형을 적어준 뒤에 괄호를 닫고,
반환할 함수의 인수의 자료형을 나란히 괄호 안에 적어주면 된다.