일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- c# 윈폼
- jupyter lab
- 유니티
- Unity
- Python
- 깃
- c# winform
- c# 추상 클래스
- C++
- Algorithm
- C언어 포인터
- Flutter
- c#
- docker
- vim
- git
- Houdini
- 도커
- C# delegate
- 다트 언어
- dart 언어
- 포인터
- HTML
- 플러터
- gitlab
- 구조체
- Data Structure
- jupyter
- github
- c언어
- Today
- Total
목록전체 글 (507)
nomad-programmer
카운터처럼 작동하는 함수를 다음과 같이 작성할 수 있다. int count = 0; int counter() { return count++; } 이 코드에 문제가 무엇일까? 이 코드는 count라는 전역 변수를 사용한다. 이 변수가 전역 변수이므로 어떤 함수도 count의 값을 바꿀 수 있다. 큰 프로그램을 만들 때는 엉뚱하게 전역 변수를 바꾸는 실수를 범할 수 있기 때문에 너무 많은 전역 변수를 사용하는 건 좋지 않다. 다행히도 C는 전역 메모리에 저장하지만 특정 함수나 파일만 사용할 수 있는 변수도 만들 수 있다. // static 키워드를 사용하면 counter() 함수가 종료되어도 이 값이 유지된다. int counter() { // count는 전역 변수지만, 이 함수만 이 변수에 접근할 수 있..
뮤텍스는 공유 데이터를 보호하는 락이다. 여러 스레드가 한 변수를 동시에 갱신하면, 결과는 예측할 수 없는 상태가 된다. #include #include #include #include #include // 2백만개 int number = 2000000; void error(char *msg) { fprintf(stderr, "%s: %s", msg, strerror(errno)); exit(1); } void *decay_number(void *t) { for (int i = 0; i < 100000; i++) { number = number - 1; } return NULL; } int main(int argc, char *argv[]) { void *result; int ste; pthread_t ..
프로세스가 언제나 답이 되지는 못한다. 다음과 같은 이유가 있기 때문이다. 프로세스를 생성하려면 시간이 걸린다. 어떤 컴퓨터는 프로세스를 새로 만들려면 시간이 꽤 오래 걸린다. 아주 오랜 시간은 아니지만 약간의 시간이 걸린다. 추가로 수행할 일이 0.02~0.03초 걸리더라도 매번 프로세스를 새로 만드는 건 그리 효율적이지 않다. 프로세스는 데이터를 공유하기가 까다롭다. 자식 프로세스를 만들면 부모 프로세스의 모든 데이터를 완전히 복사하게 된다. 그러나 데이터를 복사했으므로, 자식 프로세스가 부모 프로세스에 데이터를 다시 보내려면 파이프와 같은 메커니즘을 사용해야 한다. 프로세스는 그저 어렵기만 하다. 프로세스를 생성하려면 많은 코드를 짜야 한다. 그러면 프로그램은 길어지고, 지저분해지기 마련이다. 스레..
인터넷에 있는 대부분의 저수준 네트워킹 코드는 C로 작성되었다. 네트워크로 연결되는 애플리케이션은 서버와 클라이언트, 두 프로그램이 필요하다. 서버는 동시에 여러 클라이언트와 통신할 수 있어야 한다. 클라이언트와 서버는 프로토콜(protocol)이라는 구조화된 통신 방법을 사용한다. 인터넷에서는 다양한 프로토콜이 사용된다. 이 중에 인터넷 프로토콜(Internet Protocol, IP)과 같은 저수준 프로토콜은 인터넷으로 1과 0으로 된 이진값을 전송하는 방법을 정의한다. 하이퍼텍스트 전송 프로토콜(HyperText Transfer Protocol, HTTP)과 같은 고수준 프로토콜은 웹브라우저와 웹 서버가 통신하는 방법을 정의한다. 클라이언트와 서버는 프로토콜이라는 구조화된 통신 규약을 따른다. C프..
운영체제는 시그널로 프로그램을 제어한다. 시그널은 단지 정수형의 짧은 메시지일 뿐이다. 시그널이 도착하면 프로세스는 하던 일을 멈추고 시그널을 처리해야 한다. 프로세스는 시그널과 시그널 처리기라는 함수를 대응시키는 시그널 매핑 테이블을 살펴본다. 인터럽트 시그널에 대한 기본 시그널 처리기는 단지 exit() 함수를 호출한다. 시그널을 잡아 직접 정의한 코드 실행 때론 프로그램을 인터럽트 걸 때 직접 정의한 코드를 실행하고 싶을 것이다. 예를 들어 프로세스가 열린 파일이나 네트워크 연결을 갖고 있으면 프로그램을 종료하기 전에 리소스를 닫고 정리하고 싶을 것이다. 이럴때 "sigaction" 을 사용하면 코드를 실행하고 명령시킬 수 있다. sigaction은 함수 랩퍼이다. sigaction은 함수에 대한 ..
자식 프로세스가 생성하는 데이터를 실시간으로 읽는 방법 pipe() 함수는 데이터 스트림 두 개를 연다. # 데이터 스트림 0 표준 입력 1 표준 출력 2 표준 에러 3 파이프의 읽는 쪽 (ex: fd[0]) 4 파이프의 쓰는 쪽 (ex: fd[1]) 자식 프로세스가 부모 프로세스에 데이터를 보내야 하므로, 자식 프로세스의 표준 출력과 부모 프로세스의 표준 입력에 연결된 파이프가 필요하다. 파이프는 pipe() 함수로 생성한다. 이 함수는 연결된 두 스트림을 만들어 테이블에 추가한다. 한쪽 스트림에 쓴 데이터는 다른 쪽 스트림에서 바로 읽을 수 있다. pipe() 함수가 디스크립터 테이블에 두 항목을 만들 때, 디스크립터들을 항목이 두 개 있는 배열에 저장한다. // 디스크립터들이 배열에 저장됨 int ..
파일 디스크립터는 데이터 스트림을 나타내는 숫자이다. 데이터 스트림은 말 그대로 프로세스로 들어가고 나오는 데이터의 흐름이다. 표준 입력, 출력, 에러에 대한 데이터 스트림이 있으며, 파일이나 네트워크 연결과 같은 데이터 스트림도 더 만들 수 있다. 프로세스의 출력을 리다이렉션하면 데이터를 보낼 곳을 바꿀 수 있다. 따라서 표준 출력이 하면 대신 파일에 데이터를 보낼 수 있다. 모든 프로세스는 스택과 힙 데이터 공간 외에도 자신이 실행하는 프로그램을 포함하고 있다. 그런데 표준 출력과 같은 데이터 스트림이 어디에 연결되는지 어딘가에 기록해놓아야 한다. 각 데이터 스트림은 파일 디스크립터(File Descriptor)에 의해 표현되는데, 프로그램에서는 단지 숫자로 나타난다. 프로세스는 파일 디스크립터와 이..
exec() 함수를 호출하면 새로운 프로그램을 실행해 현재 함수를 대체한다. 그러면 원래 프로그램은 곧바로 종료된다. fork()는 프로세스를 복제한다. fork()는 현재 프로세스의 완전한 사본을 만든다. 새로 만든 사본은 똑같은 프로그램을 똑같은 위치에서 실행한다. 똑같은 변수를 갖고 있고 변수 안에 들어 있는 값도 똑같다. 단 하나 차이점이라면 사본 프로세스의 PID가 원래 프로세스와 다르다는 것이다. 원래 프로세스는 "부모 프로세스"라고 하며, 새로 생성된 사본은 "자식 프로세스"라고 부른다. 리눅스 및 맥과는 달리 윈도우는 기본적으로 fork()를 지원하지 않는다. 프로세스는 누가 부모이고 누가 자식인지 알 수 있는 방법이 필요하다. 따라서 fork() 함수는 자식 프로세스에는 0을, 부모 프로..
errno 변수는 errno.h 헤더 파일에 정의된 전역 변수이다. 이 변수에는 다음과 같이 표준적으로 정의된 에러 값이 저장된다. EPERM=1 허용되지 않은 연산 ENOENT=2 지정한 파일이나 디렉토리가 없다 ESRCH=3 지정한 프로세스가 없다 이 값으로 errno 변수를 검사할 수 있다. 아니면 string.h 헤더 파일에 정의도니 strerror() 함수를 사용해 표준 에러 메시지를 만들 수 있다. // strerror() 함수는 에러 번호를 메시지로 변환한다. puts(strerror(errno)); 따라서 시스템이 실행하려는 프로그램을 찾지 못하면 errno 변수를 ENOENT로 설정하며, 이 번호는 다음과 같은 표준 에러 메시지에 대응된다. No such file or directory ..
system() 함수를 호출하면 운영체제는 명령 문자열을 해독해 어떤 프로그램을 어떻게 실행할지 결정해야 한다. 여기에는 문제가 발생한다. 운영체제가 문자열을 해독해야 한다는 점이다. system() 함수는 치명적인 단점을 가지고 있다. 이 함수를 사용하기 쉽지만 보안이 허술하다. 예를 들어, echo ' ' >> reports.log 명령을 내리는데 누군가 다음과 같은 명령을 입력하면 어떻게 될까? echo ' ' && ls / && echo '' >> reports.log 명령행에서 실행할 명령을 문자열 안에 삽입(Injection)하면 프로그램은 입력된 내용이 무엇이든 그대로 실행한다. 이런 모호함을 제거하고 어떤 프로그램을 실행하려는지 운영체제에 정확히 알려주도록해야 한다. "exec()" 함수는..

동적 라이브러리를 사용하면 실행 시에 코드를 바꾸기 쉽다. 프로그램을 다시 컴파일할 필요 없이 애플리케이션을 갱신할 수 있다.만약 여러 프로그램이 똑같은 코드를 공유하고 있다면 모든 프로그램을 한꺼번에 모두 갱신할 수 있다. 정적 라이브러리와 동적 라이브러리 중 어떤 것이 더 좋은가? 상황에 따라 다르다. 정적 라이브러리는 컴퓨터 간에 이동하기 쉽게 더 작고 빠른 실행 파일을 만든다. 동적 라이브러리를 사용하면 실행 시에 프로그램의 환경을 더 많이 바꿀 수 있다. 오브젝트 파일 혹은 정적 라이브러리 파일을 링크하고 빌드하면 정적 프로그램이 된다. 별개로 있던 여러 오브젝트 코드로 하나의 실행 파일을 만들고나면, 프로그램을 새로 빌드하지 않고서는 들어간 코드를 바꿀 방법이 없다는 이야기다. 프로그램은 그저..

오브젝트 파일(*.o)들을 정적 라이브러리 파일(*.a, *.lib)로 만들어서 사용하면 좋은 점 오브젝트 파일에 존재하는 함수를 하나라도 사용한다면, 빌드할 때 오브젝트 파일의 모든 코드를 가져와 하나의 파일로 빌드된다. 하지만 오브젝트 파일들을 정적 라이브러리로 만들게되면, 사용하고 있는 오브젝트 코드만 가져와 하나의 파일로 빌드된다. 즉, 정적 라이브러리가 아닌 오브젝트 파일을 쓰게 된다면 사용하지도 않는 오브젝트 코드들도 모두 함께 빌드되어버린다. 때문에 파일의 크기가 커지고 느려진다. 오브젝트 파일이 아닌 정적 라이브러리 파일을 쓰도록 하자. 먼저 오브젝트 파일을 생성한다. gcc -I test_code.c encrypt.o checksum.o -o test_code 이런식으로 프로그램을 컴파일..