C language - 5. array of pointer and pointer to array(포인터 배열과 배열 포인터)


1. 포인터 배열

포인터 배열은 포인터로 선언되어 주소값이 배열 형태로 저장되는 변수입니다.

아래 표와 같이 포인터 배열은 단순히 주소값을 저장하는 포이터변수가 배열처럼 나란히 붙어서 선언된것 입니다.


2. 배열 포인터
1차원의 배열을 함수의 매개변수로 보낼때나 배열의 주소를 알아낼때 배열의 이름을 그대로 포인터 형태로 사용하게 됩니다. 그러나 다차원 배열을 이렇게 사용하게되면 1차원만큼만 가져오게됩니다. 그래서 사용하는것이 배열 포인터입니다.


배열 포인터는 '변수타입 (* 변수명)[n];'의 형태로 선언합니다. 이때 배열 포인터는 변수타입의 크기만큼의 포인터가 n칸 만큼 뛰어넘어 가리킨다는것을 뜻합니다. 설명으론 이해하기 힘드므로 예제의 내용을 설명하면
예제에서 배열 포인터 ptr은 위와 같이 가리키게 됩니다. ptr[i][0]은 가리키는 배열의 0번째 열을 ptr[i][1]는 1번째 배열만을 계속해서 가리키게 됩니다.

출력결과

사실 다차원 배열이라고 해서 위의 표처럼 저장되는것이 아닙니다.

arr3의 배열은 위 표처럼 순서대로 저장됩니다. 처음 배열 포인터를 설명할때 선언된 n의 크기만큼 뛰어 넘는다는 설명을 했는데 실제로 배열 포인터가 하나씩 가리키는 주소가 아래의 그림처럼 선언된 int의 크기에 선언된 n의 크기만큼 뛰어넘어가면서 가리키기 때문입니다.

포인터 배열과 배열포인터를 설명했습니다. 이 둘은 각각 주소값을 저장하는 포인터를 배열 형태로 선언 한다는것과 다차원 배열을 가리키는 포인터라는 점에서 서로 의미가 다른데 모양이 비슷하여 혼동되기 쉽습니다.

int * parr[n];
int (*arrp)[n];

보시는 바와 같이 배열포인터에 소괄호가 있다는것에서 차이가 나게됩니다.

multithreading network programming - 3. multi client echo server(멀티스레드 네트워크 프로그래밍 - 다중접속 에코서버)



13라인의 idx를 이용해 클라이언트의 번호를 저장합니다. 14라인의 socklist[100]에 클라이언트 정보를 저장합니다.(총 100명) 26번 라인에서 socklist를 전부 -1로 초기화해줍니다. 51,52라인을 통해 클라이언트 소켓을 socklist에 클라이언트 소켓정보를 저장하고 idx의 값을 증가시켜줍니다. 76라인을 통해 클라이언트 소켓이 저장되지 않은 -1이 나올때까지 반복문을 돌리면서 저장된 모든 클라이언트에게 메세지를 전송합니다.

C language - 4. string function(문자열 함수)

<string.h>의 헤더파일에는 문자열과 관련되있는 함수들을 정의하고 있습니다.


1. 문자열 복사

 문자열을 복사하는 함수에는 strcpy()와 strncpy()가 있습니다. strncpy() 함수에서 str3보다 1작은 크기만큼 복사해야하는데 문자열에는 마지막에는 반드시 null문자(\0)가 들어가야합니다.

문자열의 출력함수는 null문자가 나타날때까지 출력하는데 null문자를 넣을 공간을 만들어주지 않으면 출력함수는 null문자를 찾아 엉뚱한 영역의 값까지 출력해버립니다. 그러므로 마지막 열의 null값을 넣어 주어야 합니다.

null값을 무시하여 짠 출력결과
null값을 넣어준 출력결과


2. 문자열 덧붙임

문자열을 덧붙이는 strcat()와 strncat()입니다. 문자열을 덧붙일때는 아래의 그림과 같이 null문자의 자리부터 덧붙여집니다.
strncat()함수는 덧붙일 문자열에서 원하는 수만큼만 덧붙게 해줍니다.


3. 문자열 비교

문자열의 비교는 두 문자열을 한 문자씩 비교하여 결정합니다. 비교했을 때 반환되는 값은 아래의 표와 같이 나오게 됩니다.
이 때 문자열의 크고 작음을 비교하는 기준은 아스키코드값을 토대로 일어나게 됩니다. 예를 들어 문자열 "ABC"와 "ACD"를 비교할때 먼저 A는 서로 같기 때문에 다음 문자로 넘어갑니다. B와 C를 비교할때는 아스키 코드값이 C가 더 크므로 strcmp 함수는 0보다 큰 값을 반환하게 됩니다.


4. 문자열 길이

문자열의 길이를 반환하는 strlen() 함수는 문자열의 null값을 제외한 길이를 반환합니다.
위의 예제에서 문자열은 7의 공간을 차지하고 있지만 null값을 제외하여 6의 값이 반환됩니다.


5. 문자열 분리

strtok() 함수는 매개변수로 문자열과 분리하는 구분자를 받습니다. 분리하는 구분자는 큰따옴표("")안의 문자 혹은 띄어쓰기로 해당되는 문자의 이전 문자까지만을 반환합니다. 이후 매개변수로 받았던 문자열은 처음 자른 문자열로 바뀌고 자르고 남은 문자열은 NULL에 전달되며 더이상 없다면 NULL을 반환합니다.

출력결과


add system call in linux 3.8 (리눅스 3.8 시스템 콜 추가)

32bit 리눅스의 경우

1. (커널 폴더)/arch/x86/syscalls/syscall_32.tbl에 아래와 같이 351번을 추가해줍니다.

64bit 리눅스의 경우

1. (커널 폴더)/arch/x86/syscalls/syscall_64.tbl에 아래와 같이 314번을 추가해줍니다.


아래부터는 공통입니다.

3. (커널 폴더)/include/linux/syscalls.h에 아래와 같이 addcall 원형을 추가해줍니다.


4. (커널 폴더)/kernel 안에 addcall.c를 아래와같이 작성한 뒤 gcc -c addcall.c를 해줍니다.


5. (커널 폴더)/kernel 안의 Makefile에 다음과 같이 추가해줍니다.


6. 커널 컴파일을 해줍니다.

7. 테스트 프로그램을 작성합니다.



8. 아래와 같이 결과값을 확인 할 수 있습니다.(오른쪽)
    dmesg를 이용하면 아래와같이 [kernel] i = 5 를 확인 할 수 있습니다.(왼쪽)





C language - 3. file I/O(파일 입출력)

 파일 입력함수에는 getc(), fgetc(), fgets(), fscanf(), fwrite()가 있고 파일 출력함수에는 putc(), fputc(), fputs(), fprintf(), fread()가 있습니다.



1. 텍스트 파일 입출력

#include <stdio.h>

void main()
{
       FILE * fp1 = fopen("input.txt", "rt"); // input.txt 파일을 읽기용으로 선언
       FILE * fp2 = fopen("output.txt", "wt"); // output.txt 파일을 쓰기용으로 선언
       if(fp1 == NULL || fp2 == NULL) {
              puts("파일 오픈 실패");
              return;
       }
       char str[30];
       int ch1, ch2;

       ch1 = getc(stdin);  // steam부분에 stdin을 넣어서 표준 입력함수와 같은 효과를 보인다
       ch2 = fgetc(stdin);
       fgets(str, sizeof(str), fp1); //fp1의 내용을 읽어 들여 str크기의 str 변수에 복사

       putc(ch1, stdout);
       fputc(ch2, stdout);
       fputs(str, sizeof(str), fp2); // 문자열을 출력하지만 개행은 되지 않는다

       fclose(fp1); // fopen으로 열어준 FILE 포인터형은
       fclose(fp2); // 반드시 fclose로 닫아주어야한다

}


FILE구조체는 코드 밖의 파일에서 값을 읽어오거나 저장하기 위해 사용합니다. FILE의 포인터형으로 선언후 fopen 함수를 사용해 파일의 위치와 개방모드를 매개변수로 입력하며 개방모드는 아래의 표와 같이 사용합니다.

fopen 함수는 운영체제가 파일을 열기위해 자원을 따로 할당해주어서 사용되기때문에 성능저하를 막기위해 그리고 출력버퍼에 기다리고있던 값들을 파일에 저장하여 손실을 막아주므로 사용한뒤 반드시 끝나기전에 fclose 함수를 사용하여 닫아주어야 합니다.

getc(), fgetc(), putc(), fputc()함수의 c는 모두 char변수의 c를 약자로 쓰고있어서 문자 하나를 입출력하는 함수이고 fgets(), fputs()는 문자열을 의미하는 string의 s이며 이는 표준 입출력함수들과 비슷하며 파일입출력함수의 f는 file의 약자를 의미합니다.


2. 바이너리 파일 입출력


#include <stdio.h>

void main()
{
       FILE * fp1 = fopen("input.bin", "rb"); // 바이너리 파일 읽기용으로 선언
       FILE * fp2 = fopen("output.bin", "wb"); // 바이너리 파일 쓰기용으로 선언
       if(fp1 == NULL || fp2 == NULL) {
              puts("파일 오픈 실패");
              return;
       }
       int count;
       char str[20];

       while(1)
       {
              count = fread((void*)str, sizeof(char), (sizeof(str)/sizeof(char)), fp1); // fp1으로 파일을 읽어들여서 sizeof(str)의 크기만큼 str에 읽어들임
              if(count < (sizeof(str)/sizeof(char)))
              {
                     if(feof(fp1)!=0) // 파일이 실패했는지 확인 성공시 0이 아닌 값을 반환
                     {
                            fwrite((void*)str, sizeof(char), count, fp2);
                            puts("복사 완료");
                            break;
                     }
                     else
                            puts("복사 실패");
                     break;
              }
              fwrite((void*)str, sizeof(char), (sizeof(str)/sizeof(char)), fp2); // 참조
       }

       fclose(fp1);
       fclose(fp2);
       return;
}


처음으로 fread 함수를 실행할 때 읽어들인 바이너리 파일이 char형 변수 str보다 클 때 str의 최대 크기만큼만 읽어들인뒤 '참조'부분에서 fwrite를 실행합니다. 계속해서 while문을 왕복하면서 바이너리 파일을 쓰다가 fread에서 남은 바이너리 파일이 str보다 작으면 count에는 str보다 작은 값이 입력이 됩니다. 이 때 첫 if문에선 count의 값이 작기 때문에 내부로 가게 되는데 파일의 끝에 도달해서 내부로 왔는지 함수의 실패로 왔는지 확인하기위해 파일의 끝에 도달했을시 0이 아닌값을 반환하는 feof함수를 써서 확인후 남은 크기의 값을 바이너리파일에 마지막으로 쓴다음 while문을 나옵니다


add system call in linux 2.6 (리눅스 2.6 시스템 콜 추가)

32bit 리눅스의 경우

1. (커널 폴더)/arch/x86/kernel/syscall_table_32.S에 아래와같이 sys_addcall을 추가합니다.


2. (커널 폴더)/arch/x86/include/asm/unistd_32.h에 아래와같이 341번을 추가하고
 #define NR_syscalls 의 넘버를 +1 증가시켜줍니다.


64bit 리눅스의 경우

3. (커널 폴더)/arch/x86/include/asm/unistd_64.h에 아래와같이 add부분을 추가해줍니다.


아래부터는 공통입니다.

4. (커널 폴더)/include/linux/syscalls.h에 아래 /**add**/부분과 같이 함수 원형을 선언해줍니다.


5. (커널 폴더)/kernel 안에 addcall.c를 아래와같이 작성한 뒤 gcc -c addcall.c를 해줍니다.


6. (커널 폴더)/kernel 안의 Makefile에 다음과 같이 추가해줍니다.


7. 커널 컴파일을 실행해줍니다.

8. 테스트 프로그램을 작성합니다.


9. 아래와 같이 결과값을 확인 할 수 있습니다.(오른쪽)
    dmesg를 이용하면 아래와같이 [kernel] i = 5 를 확인 할 수 있습니다.(왼쪽)






install wiringpi on raspberry pi(라즈베리파이 - 링파이 설치)

WiringPi

WringPi는 라즈베리파이의 GPIO포트를 컨트롤하기 위해 제공되는 라이브러리입니다.
아래 주소에서 API 함수를 확인 할 수 있습니다.

홈페이지의 다운로드 and 인스톨 방법입니다.
https://projects.drogon.net/raspberry-pi/wiringpi/download-and-install/

1. 먼저 sudo apt-get install git-core로 git-core을 설치해줍니다.

2. git clone git://git.drogon.net/wiringPi 로 wiringPi 설치파일을 다운받아줍니다.

3. cd wiringPi 링파이 폴더로 이동한 뒤

4. ./build를 실행해줍니다.

링파이가 잘 인스톨 되었는지 확인하기위해

gpio -v(버전확인)과

gpio readall을 실행시켜봅니다.

링파이로 코딩을 할 때엔 라즈베리파이의 GPIO 핀 넘버와 wiringPi의 핀 넘버가 같지 않습니다.

홈페이지에 첨부된 아래 표를 확인하시면됩니다.

GPIO 23번은 wiringPi에서는 4번,
GPIO 24번은 wiringPi에서는 5번입니다.







C language - 2. standard I/O(표준 입출력)

1. 표준 입출력
 C언어에서 입출력 함수는 표준 입출력함수와 파일 입출력함수로 나뉩니다. 파일 입출력은 외부에서 값을 입력받거나 저장하는 반면에 표준 입력함수는 일반적으로 키보드의 입력을 받고 표준 출력함수는 일반적으로 모니터에 출력을 하게됩니다.

표준 입력함수에는 getchar(), gets(), scanf()가 있고 표준 출력함수에는 putchar(), puts(), printf()가 있습니다.





위 예제를 보면 int형 변수 ch에 문자 하나를 입력받아 문자 하나를 출력하게 됩니다. 이때 왜 char형을 나두고 int형으로 받는가 하면 getchar()와 putchar()함수는 함수의 호출을 실패했거나 Ctrl + Z키가 입력을 받으면 EOF(End Of File)이라는 -1의 수를 반환하는데 char의 형태로 받게되면 char를 unsigned char로 인식하는 컴파일러도 있기 때문에 -1을 정확히 보관한다고 보장할 수 없습니다.

왜 C언어에서 가장 많이 쓰는 scanf()와 printf()를 나두고 왜 문자 하나 밖에 받지 못하는 getchar()와 putchar()를 쓰냐면 getchar()는 scanf()에 비해 훨씬 적은 메모리를 차지하는데 (putchar()와 printf()도 마찬가지) 많은 값을 원하는것도 아니고 하나의 문자를 원한다면 getchar()를 사용하여 더 효율적으로 짜기 위해서 입니다.


#include <stdio.h>

void main()
{
      char str[10];

      gets(str);  // \0(널 문자) 제외 9문자 저장
      puts(str);  // \0이 나올때 까지 출력하고 개행
}


getchar는 문자하나를 받는 반면에 gets는 문자열을 입력받게 됩니다.


위 두 예제를 보면 둘 다 #include <stdio.h>가 보이는데 이 뜻은 stdio.h(표준입출력이 들어있는 헤드파일)를 이 프로그램과 합치라는 것 입니다.


multithreading network programming - 2. echo server(멀티스레드 네트워크 프로그래밍 - 에코서버)

간단한 에코서버입니다.














41번 라인에서 accept 한 후 리턴받은 connSock의 값을

43번 라인에서 쓰레드를 생성하면서 값으로 넘겨줍니다.

그럼 47번 라인에 생성된 쓰레드 내부의 54번라인 반복문 안에서

클라이언트와 지속적으로 통신을 받아서 받은 내용을 돌려주게됩니다.

compiling the linux kernel (리눅스 커널 컴파일)

커널 컴파일을 설치하기 전에 필요한 패키지를 설치해줍니다.

sudo apt-get install build-essential kernel-package libncurses5-dev libqt3-headers libncurses5-dev

CentOs경우 yum install ncurses-devel

다음으로 www.kernel.org/pub/linux/kernel/ 에 접속하여 자신이 설치할 버전의 커널을 다운받습니다.


1. 다운받은 linux-"   ".tar.gz를 /usr/src로 옮겨줍니다.

2. tar xvzf linux-" ".tar.gz를 이용해 압축을 풀어줍니다.









3. 압축을 푼 뒤 linux 폴더 안으로 이동합니다.



4. make mrproper (모든 생성 파일, config, 여러 backup 파일들 제거)와

5. make clean (컴파일 후 생성된 여러 object파일들을 삭제해 줍니다.)를 해줍니다.

6. 이후 cp -p /boot/config-지난 버전 ./.config 이전 버전의 설정파일을 가져와 사용합니다.

7. 그리고나서 make menuconfig를 쳐주면 아래와 같은 화면이 뜹니다.


8. 위와 같은 화면이 뜨면 아래 Load and Alternate Configuration File 에 복사한 .config가 들어있는지 확인해줍니다.

9. General startup메뉴로 이동해

10. enable deprecated sysfs features to support old userspace tools(NEW)를 선택해줍니다.

11. Local Version 선택 후 이름을 입력해 줍니다. 예) -3.13.7

12. 저장 후 밖으로 나옵니다.

13. make all (전체 컴파일로 커널컴파일 bzImage가 생성됩니다.)

14. make modules ( 모듈로 설정한 파일을 컴파일합니다.)

15. make modules_install (필요시 사용할 수 있도록 설정합니다.)

16. make install


오랜시간 커널컴파일을 하게됩니다.

17. 컴파일이 끝나면 reboot 해줍니다.

아래와 같이 설치 전과 설치 후의 버전이 다른 것을 확인 할 수 있습니다.


C language - 1. primitive data type(기본 자료형)

1. 변수란?

 프로그래밍 언어에서 값을 저장하거나 저장된 값을 읽거나 다른 값을 넣을 수 있는 공간을 할당받는 것을 변수라고 합니다. 이러한 변수들의 크기를 크게 잡으면 광활한 범위의 값을 저장할 수 있겠지만 작은 값을 필요로 하는 변수에 비해 훨씬 큰 공간을 할당해주게 되면 메모리의 낭비가 발생하게 됩니다. 그렇기 때문에 프로그램을 짤 때 선언한 변수들의 예상범위내의 변수의 크기를 적절하게 조절하여 메모리를 효율적으로 사용할 수 있게 만들어야 되겠습니다.







2. 기본 자료형

 기본 자료형은 정수형 변수와 실수형 변수로 나뉩니다.

정수형 변수에는 기본적으로 char, short, int, long이 제공됩니다. 하나의 char변수에는 1byte(=8bits)의 크기가 할당되고 가장 왼쪽 비트가 0일때 양수(+) 1일때 음수(-)로 표현이 되고 나머지 7bits의 범위로 숫자가 표현이되는데 위 표에서 양수가 표현범위가 1작은것은 0을 양수에 포함하기 때문입니다.

정수형 변수에는 char, short, int, long이 있다고 설명했는데 위에 표에는 앞에 unsigned가 붙은 변수들이 있습니다. 사실 unsigned가 붙지 않은 변수들은 signed가 생략되었다고 생각하시면 됩니다. 앞에 unsigned가 붙게 되면 바로 전에 설명한 가장 왼쪽 비트가 양수와 음수를 표현하지 않고 나머지 비트와 함께 숫자를 표현하게 됩니다. 그리하여 signed의 변수들보다 표현범위가 약 2배 증가하게 됩니다.


그렇다면 int와 unsigned int의 범위는 어떻게 될까요?

1word의 크기를 일반적으로 많이 쓰이는 4bytes라고 가정할 때 int는 -2147483648 ~ 2147483647이고, unsigned int는 0 ~ 4294967296의 범위를 가지게 됩니다.


실수형 변수에는 signed인 정수형과 마찬가지로 가장왼쪽 1bit는 양수와 음수를 표현하고 나머지를 지수부분과 가수부분으로 나눕니다. 실수형 변수에 실수를 입력하게 되면 컴퓨터가 x.xx×10^n 으로 나타내주고 이때 n은 지수부분에 x.xx는 가수부에 들어가서 저장되어집니다.







multithreading network programming - 1. multithread(멀티스레드 네트워크 프로그래밍)






 멀티스레드는 프로세스 내부에서 두가지의 작업을 동시에 실행하는것을 의미합니다. fork의 경우 자기 자신과 똑같은 완전 새로운 프로세스를 복제하지만 thread의 경우 원하는 작업을 테스크 형태로 생성합니다.




 간단한 Thread 예제


#include <stdio.h>
#include <pthread.h>
#define NUM_THREADS 3

void *thread_ (void *arg)
{
       printf("get_id[%d] = %lu \n", arg, pthread_self());
       return arg;
}

main()
{
       pthread_t tid[NUM_THREADS];
       int i, status;

       for(i=0; i < NUM_THREADS; i++) {
              status = pthread_create (&tid[i], NULL, thread_, (void *) i );
       }

       pthread_exit(NULL);
}



이 예제는 쓰레드를 세개 생성하여 쓰레드 고유 id를 출력하는 예제입니다.

위의 pthread_create 함수를 이용해 쓰레드를 생성합니다.

내부 인자로는

1. &tid[i] -> 스레드가 생성될때 스레드의 ID가 저장될 위치의 주소를 가르킵니다.

2. NULL -> 생성할 스레드의 속성을 설정할때 사용하는데 대부분 NULL을 사용합니다.

3. thread_ -> 쓰레드가 실행시킬 (void *)형의 함수를 가르킵니다. 위 코드에선 선언된 *thread_ 함수를 불러옵니다.

4. (void *) i -> 쓰레드에 의해 호출되는 함수에 전달하는 인자입니다. thread_함수에선 *arg로 받게됩니다.

pthread_create 함수의 수행이 성공적으로 끝나면 0을 실패하면 오류코드를 반환합니다.

위 예제에서는 define된 NUM_THREADS만큼 for문 내에서 스레드를 생성합니다.

생성된 스레드는 각각 0과 1,2를 thread_함수로 넘겨주고 함수내에서는 전달받은 인자와 pthread_self()를 이용해 쓰레드의 고유 id를 출력해줍니다.

출력결과입니다.