나만의 라이브러리 만들기
C에서도 라이브러리를 만들 수 있다
- 오브젝트 파일을 모아 라이브러리로 만듦
- 다시 컴파일할 필요 없이 코드 재활용이 가능
- 소스 코드 공개 없이(단, 헤더 파일은 예외) 라이브러리 배포 가능
- C에서는 두 종류의 라이브러리를 만들 수 있다고 했음
- 정적 라이브러리
- 동적 라이브러리
(복습) 정적 라이브러리와 링크
- 정적 라이브러리와 링크하는 것을 정적 링킹이라고 함
- 라이브러리 안에 있는 기계어를 최종 실행파일에 가져다 복사함
- 동적 링킹에 비해
- 실행 파일의 크기가 커짐
- 메모리를 더 잡아먹을 수 있음
- 실행 속도가 빠름
보통 정적 라이브러리를 사용하는 절차
- 소스 코드들을 컴파일하여 정적 라이브러리를 만듦
- 보통 파일 하나
- 물론 여러 개의 라이브러리를 만들면 파일도 여럿
- 확장자는 *.lib(윈도우 비주얼 스튜디오) 또는 *.a(리눅스 계열)
- 참고로 리눅스 계열에서는 아카이브(archive)라고도 함
- 다른 소스 코드들을 작성할 때 위 라이브러리의 헤더 파일들을 사용
- 실행 파일을 만들기 위해 main() 함수를 가지고 있는게 보통
- 여기의 소스 코드들이 바뀌어도 정적 라이브러리를 다시 만들 필요 없음
- 컴파일 할 때 정적 라이브러리와 함께 링킹
- simple_math.c 컴파일하기
clang -std=89 -W -Wall -pedantic-errors -c simple_math.c -o simple_math.o
- 정적 라이브러리 만들기
- 플랫폼마다 이용하는 실행파일이 다름
- 윈도우 : llvm-ar
- 리눅스 계열 : ar
llvm-ar
llvm-ar -명령어<modifier> 정적_라이브러리_파일 <o파일들>
-
명령어
- r : 정적_라이브러리_파일에 o파일들을 추가
- d : 정적_라이브러리_파일에서 o파일들을 삭제
-
modifier(선택)
- 각 명령어마다 사용할 수 있는 modifier가 다름
- c : 정적 라이브러리 파일이 처음 만들어질 때 경고 메시지 출력 안 함
-
그 밖의 명령어와 modifier가 궁금하면 스스로 찾아 볼 것
-
아래 예 둘 다 simple_math.lib 파일이 없는 경우
-
'c'를 사용하지 않을 때
> llvm-ar -r simple_math.lib simple_math.o llvm-ar.exe: warning : create simple_math.lib
-
'c'를 사용할 때
> llvm-ar -rc simple_math.lib *.o
llmv-ar으로 정적 라이브러리 만들기
week12\simple_math> llvm-ar -rc ..\lib\simple_math.lib simple_math.o
- 정적 라이브러리의 헤더 인클루드하기
// main.c
#include <stdio.h>
#include "simple_math.h"
int main()
{
printf("Test: %d\n", add(10, 20));
return 0;
}
- 정적 라이브러리에 있는 함수를 사용하려면 해당 헤더를 인클루드
- 이 때 헤더 파일의 경로는 생략하는 게 일반적
- 아직 함수의 실제 구현은 모름
- 정적 라이브러리와 함께 빌드하기
clang -std=c89 -W -Wall -pedantic-errors -I <dir> -L <dir> -l<lib_name> *.c
- -I dir
- 인클루드 할 때 헤더파일을 검색할 경로를 추가
- -L dir
- 빌드 시 사용할 라이브러리 파일이 있는 폴더
- -l lib_name
- lib_name: 빌드 시 사용할 라이브러리
- 파일명.lib에서 파일명을 -l 다음에 띄어쓰기 없이 붙임
simple_math.lib과 함께 빌드해보자!
week12\program> clang -std=c89 -W -Wall -pedantic-errors -I "../simple_math" -L "../lib" -lsimple_math *.c
-
simple_math.h는 week12\simple_math에 있음
-
따라서 -I를 이용해 해당 경로를 알려줌
- 안 그러면 헤더 파일을 찾지 못 함
- 이게 소스코드에서 경로를 넣어줄 필요가 없던 이유
-
정적 라이브러리 파일은 week12\lib에 있음
-
따라서 -L을 이용해 해당 경로를 알려줌
-
정적 라이브러리 파일명은 'simple_math.lib'
-
따라서 -l 바로 뒤에 'simple_math'를 붙여줌
-
실행파일이 제대로 빌드됨
-
이제 평상시와 같이 실행하면 끝
-
파일이 많아지면?
-
비주얼 스튜디오의 프로젝트 사용
-
C 역시 비주얼 스튜디오를 사용하면 이 프로젝트를 사용하게 됨
-
gcc나 clang의 경우 자체적으로 프로젝트 같은 것을 지원하지 않음
-
그러나 cmake를 이용하면 똑같은 일을 할 수 있음
-
프로젝트란, 라이브러리를 만들 때, 어떤 파일들을 컴파일해야 하는지, 실행파일을 만들 때, 어떤 소스코드와 어떤 라이브러리를 합쳐야 하는지 등을 적어 두는 파일
(복습) 동적 라이브러리와 링크
- 동적 라이브러리와 링크하는 것을 동적 링킹이라고 함
- 실행파일 안에 여전히 구멍을 남겨두는 방법
- 실행파일을 실행할 때 실제로 링킹이 일어남
- 이 링킹은 실행 중에 운영체제가 해줌
- 정적 링킹에 비해
- 실행파일 크기가 작다
- 여러 실행파일이 동일한 라이브러리를 공유할 수 있다 -> 메모리 절약
- 여러 실행파일이 이름은 같지만 버전이 다른 동적라이브러리를 사용한다면 DLL지옥을 맛볼 수 있다.
보통 동적 라이브러리를 사용하는 절차
- 소스 코드들을 컴파일하여 동적 라이브러리를 만듦
- 역시 파일 하나
- 확장자는 *.dll(윈도우) 또는 *.so(리눅스 계열)
- 다른 소스 코드들을 작성할 때 위 라이브러리의 헤더 파일들을 사용
- 컴파일 할 때 동적 라이브러리와 함께 링킹
- 단, 동적 라이브러리에 있는 기계어가 실행파일에 포함되지 않음
- 실행 중에 동적으로 링킹할 수 있는 정보만 포함
- 따라서 동적 라이브러리 파일도 같이 배포해야 함
동적 라이브러리와 운영 체제
- 운영체제마다 실행 파일 및 동적 라이브러리 내부 포맷이 다름
- 리눅스 계열 : ELF(excutable and linkable format) 포맷
- 윈도우 : PE(portable executable) 포맷
- 운영체제의 동적 링커(dynamic linker)
- 프로그램이 실행될 때 필요한 동적 라이브러리를 로딩 후 링킹 해줌
- 이러려면 동적 라이브러리 안에 있는 함수들을 메모리에 매핑해줘야 함
- 메모리에 맵핑할 때 필요한 정보가 위 포맷에 저장되어 있음
- 따라서 운영체제가 지원하지 않는 포맷이면 정보를 읽어오지 못 함
- 즉, 동적라이브러리는 운영체제에 종속적
- 운영체제와 컴파일러마다 동적 라이브러리를 만드는 방법이 다름
- 보통 사용하는 컴파일러
- 윈도우는 주로 비주얼 스튜디오를 이용
- 리눅스 계열은 주로 Clang이나 GCC를 이용
- Clang이나 GCC를 쓸 경우 윈도우와 리눅스에서 만드는 법이 다름
- 윈도우의 Clang을 사용할 때도 컴파일러 백엔드에 따라 또 달라짐
- MinGW
- 비주얼 스튜디오
동적 라이브러리 만들기
동적 라이브러리 링크하기
gcc <o파일 + 경로> -L<동적 라이브러리 경로> -l<동적 라이브러리 이름> -o <실행파일 이름>
gcc -shard <o파일 + 경로> -o <동적 라이브러리 파일명 + 경로>
동적 라이브러리의 장단점
- 장점
- 실행파일을 바꾸지 않고 동적 라이브러리 파일만 업데이트 가능
- 동적 라이브러리 파일을 바꾸지 않고 실행파일만 업데이트 가능
- 필요에 따라 동적 라이브러리를 선택적으로 로딩 가능
- 예: CPU 세대 별로 동적 라이브러리 파일을 만들어 둠
- 여러 실행파일들이 같은 동적 라이브러리를 소유 가능
- 단점
- 해킹 당하기 쉬움(예: DLL 인젝션)
- 라이브러리 안에 있는 함수의 메모리 주소가 동적으로 링킹 되기 때문
- DLL 지옥
- 해킹 당하기 쉬움(예: DLL 인젝션)
정적 라이브러리의 장단점
- 장점
- 함수의 주소가 공개 안 되니 보다 안전
- 정확한 버전의 라이브러리가 실행파일 안에 내포되어 잇음
- 최적화에 유리
- 단점
- CPU 세대 별로 실행파일을 만들어서 배포해야 함
- 라이브러리의 소스코드가 바뀔 때마다 실행파일을 재배포해야 함
- 실행파일의 소스코드가 바뀔 때도 마찬가지
- 실행파일의 크기가 커짐
- 실행 중 다른 실행파일들과 라이브러리 공유 불가
베스트 프랙티스 : 정적라이브러리를 쓰자
- 일단 기본적으로 정적 라이브러리를 사용할 것
- 동적 라이브러리가 필요하면 그 때 동적라이브러리로 전환
C99 표준
C99로 빌드하기
- std 옵션을 c89 대신에 c99로
> clang -std=99 -W -Wall -pedantic-errors *.c
(복습) 매크로 함수
- 함수 호출 형태가 아니라 코드 그 자체를 복붙
- 함수 호출에 따른 과부하를 막을 수 있음
- 그러나 디버깅이 아주 매우 엄청나게 힘듦
- 뿐만 아니라 가독성이 매우 떨어짐
인라인 함수
inline 반환형 함수_이름(매개변수 목록) {}
- 컴파일러에게 최적화 해달라고 알려주는 '힌트'
- 보통 매크로 함수처럼 코드를 복붙 해줌
- 즉, 함수 호출이 사라짐
- 힌트일 뿐이라 컴파일러가 무시할 수도 있음
- inlin이 없어도 컴파일러가 알아서 최적화를 해줄 수도 있음
- 복붙을 할 수 있으려면?
- 인라인 함수를 호출하는 코드를 컴파일할 때 그 함수의 구현을 알아야 함
- 따라서 인라인 함수의 구현은 소스 파일이 아니라 헤더 파일에 둠
C++의 인라인과는 다르다!
- C의 인라인은 C++에서 가져온 것
- 그러나 C++의 인라인만큼 명확하지 않음
- C에서 올바르게 작동시키려면 좀 이상한 짓을 해야함
- 그나마 다행인 점 : C의 인라인 코드는 C++에서 제대로 동작
무식하게 코드를 복붙하지 않는다!
- 매크로는 전처리기가 코드를 무식하게 토씨 하나 안 틀리게 복붙 함
- 그러다 보니 연산자 우선순위 문제가 생길 수 있음
- 매개변수 및 코드를 무조건 괄호로 감싸는 것을 권장
- 인라인 함수는 컴파일러가 컴파일 중에 함수 호출을 코드로 바꿔줌
- 사실 결과적으로는 복붙. 좀 더 융통성 있게 잘 복붙할 뿐
- 함수가 누리는 혜택을 그대로 누림
함수 구현을 알아야 복붙이 가능하다
- 복붙을 하려면 막하던, 잘하던 간에 함수 구현을 알아야
- 즉, 트렌스레이션 유닛 안에 인라인 함수의 실제 코드가 있어야 함
- 이 함수의 구현이 다른 C 파일 안에 있으면 불가능
- C 파일 별로 따로 컴파일되기 때문
- 따라서 헤더 파일 안에 실제 코드가 있어야 함
- 매크로 함수와 마찬가지
- 전처리기 단계에서 #include가 헤더의 내용을 모두 복붙
헤더에 함수의 구현부를 넣으면?
// simple_math.h
int add(int op1, int op2)
{
return op1 + op2;
}
// humanoid.c
/*
int add(int op1, int op2)
{
return op1 + op2;
}
*/
#include "simple_math.h"
void walk(...)
{
}
// bird.c
/*
int add(int op1, int op2)
{
return op1 + op2;
}
*/
#include "simple_math.h"
void fly(...)
{
}
-> 링킹 오류가 발생함
링킹 오류가 나는 이유
- 모든 .o 파일에 add()가 들어있음
- 즉, 동일한 이름의 함수가 2개나 있음
- 그 중 어떤 add()와 링킹을 해야 하는지 몰라서 오류 발생
inline 키워드로 이 함수의 용도를 표시
- 컴파일러에게 호출용 함수가 아니라 코드 교체용이라 알려줌
- 그 결과 링커가 볼 수 있는 함수 심볼을 만들지 않음
// simple_math.h inline int add(int op1, int op2) { return op1 + op2; }
- 이제 컴파일하면 아까의 링커 오류가 안 남
- 단, 다른 오류가 날 수도 있음...
inline 키워드는 그저 힌트일뿐
- 컴파일러가 해당 함수를 인라인화 한다는 보장이 없음
- 한다면 문제가 없음
- 안 한다면 문제가 됨
// simple_math.h inline int add(int op1, int op2) { return op1 + op2; }
// humanoid.c
#include "simple_math.h"
void walk(...)
{
// 코드 어딘가에서
// add() 호출
}
// bird.c
#include "simple_math.h"
void fly(...)
{
// 코드 어딘가에서
// add() 호출
}
## 인라인이 안 되면 무슨 문제가 일어날까?
- inline 키워드가 붙은 함수가 인라인이 안 됐다는 의미는?
- 여전히 실행 중에 함수 호출을 한다는 의미
- 따라서, 컴파일 단계에서는 그 함수의 시그니쳐만 기억
- 링커가 실제 함수 구현을 찾아 구멍을 메꿔줌
- 그럼 무슨 일이 일어날까? 링킹 오류가 발생
## 왜 이런 일이 발생하지?
- 아까 inline 키워드를 설명하며 했던 말
- 컴파일러에게 해당 함수를 '함수'로 쓰지 말라 함
- 그 대신 '코드 교체용'으로 쓰라고 알려줌
- 그러다보니 **컴파일러는 링커가 볼 수 있는 함수 심볼을 만들지 않음**
- 그러나 문제는 이 함수가 반드시 인라인 된다는 보장이 없음
- 인라인이 안 되면?
- 안 됐는데 함수 심볼마저 없음
- 따라서 링커 입장에서는 해당 함수를 찾을 방법이 없음
## 해결법?? : 일반 함수도 따로 만든다
- 인라인 함수와 똑같이 구현된 일반 함수가 어딘가에 존재하면 됨
- 링커가 찾을 수만 있으면 문제 없음
- 그러나 이런 방법은.. 쓸데없는 코드 중복
```c
// simple_math.h
#ifndef SIMPLE_MATH_H
#define SIMPLE_MATH_H
inline int add(int op1, int op2)
{
return op1 + op2;
}
#endif
// simple_math.c
#include "simple_math.h"
int add(int op1, int op2)
{
return op1 + op2;
}
올바른 해결법?? : extern
- 가장 좋은 방법 : 코드 중복 없이 함수 하나만 있는 것
- 인라인이 되면 인라인으로 사용
- 안 되면 일반 함수로 사용
- 그걸 가능하게 만드는 키워드가 extern
- extern을 붙이면 링커가 찾을 수 있는 함수도 만들어 줌
// simple_math.h #ifndef SIMPLE_MATH_H #define SIMPLE_MATH_H extern inline int add(int op1, int op2) { return op1 + op2; } #endif
-
중복 오류 또 나옴
extern을 함수 구현부에 붙일 때 문제점
- 이 헤더를 인클루드한 .c파일마다 이 함수가 생성됨
- 즉, inline 안 붙인 함수가 헤더에 있을 때와 마찬가지 문제
- 프로그램 전체에서 그 함수의 심볼은 딱 하나만 있어야 함
현재까지 아는 사실
- 인라인 함수가 인라인도, 일반 함수도 될 수 있게 해야 함
- 일반 함수로 만들기 위해서는 extern 키워드가 필요
- 단, 일반 함수는 심볼이 딱 한 번만 나와야 함
올바른 해결법(최종)
- 이 모든 것을 만족하는 가장 좋은 방법
- .h파일 안에 인라인 함수를 구현
- 그에 대응하는 .c파일을 만듦
- 그 파일에서 인라인 함수가 들어있는 .h파일을 인클루드
- 그 파일에서 인라인 함수를 extern 인라인 함수로 다시 선언
- 이러면 그 .c파일 안에서만 함수의 심볼이 나옴 (딱 한 개!)
- 이제 컴파일 중 인라인이 되면 헤더 파일에 있는 구현을 사용
- 인라인이 안 되면 링커가 .c 파일에서 나온 심볼을 이용해서 링킹
// simple_math.h
#ifndef SIMPLE_MATH_H
#define SIMPLE_MATH_H
inline int add(int op1, int op2)
{
return op1 + op2;
}
#endif
// simple_math.c
#include "simple_math.h"
extern inline int add(int op1, int op2);
// hunamoid.c
#include "simple_math.h"
void walk(...)
{
// 코드 어딘가에서
// add() 호출
}
// bird.c
#include "simple_math.h"
void fly(...)
{
// 코드 어딘가에서
// add() 호출
}
C++ 인라인과의 차이
- C의 인라인은 어떻게든 돌아가게 만들려고 이상한 짓을 한 느낌
- C++에서는 이런 짓을 안 해도 됨
- 헤더 파일에 구현한 인라인 함수는 자동적으로 extern
- 따라서 이 헤더 파일을 인클루드 한 .cpp파일마다 함수 심볼이 생김
- 그러나 표준에 따르면 링커가 이 여러 심볼 중에 하나만 골라서 링킹해야 함
베스트 프랙티스 : 인라인을 쓰자
- 위와 같은 이유 때문에 매크로 함수보다는 인라인이 좋음
- 특히 한 줄짜리 코드처럼 매우 간단한 함수일 때 적합
- 매크로도 마찬가지
- 그런데 C에서는 인라인보다는 매크로를 더 자주 사용
- 일단 인라인이 사용하기 매우 불편하고 헷갈림
- C89 이후 표준에 추가된 기능은 그리 널리 사용되지 않음
- 이런 문제가 없는 C++에서는 매크로 대신 거의 인라인을 사용
함수 호출자를 100% 제어할 수 없다
- 문자열 복사시, 두 메모리(원본, 복사될 공간)가 안 겹치는 게 맞음
- 그러나 호출자가 겹치는 메모리 범위를 인자로 전달하면?
- 막을 방법이 없음
- 나눗셈(/)할 때 분모로 0을 사용하는 걸 막을 수 없는 것과 마찬가지
결국 안전의 책임은 컴파일러에게로...
- 결국 많은 컴파일러가 이런 함수를 방어적으로 구현해둠
- 특히 c->어셈블리로의 변환 과정에서
- 이런 안전장치 덕에 코드가 '비교적' 안전하게 실행됨
- 그러나 그로 인한 성능 저하 문제
restrict 키워드
int printf(const char* restrict format, ...);
int fprintf(FILE* restrict stream, const char* restrict format, ...);
int sprintf(void* restrict dest, const void* restrict src, size_t count);
void* memcpy(void* restrict dest, const void* restrict src, size_t count);
char* strcpy(char* restrict dest, const char* restrict src);
- 포인터 변수 전용인 컴파일러에게 알려주는 힌트
- '이 포인터 변수의 메모리는 절대 다른 변수와 겹치지 않는다'
- 컴파일러가 이 힌드를 무시할 수 도 있음
- 메모리 범위가 겹치는 걸 막는 키워드가 아님
- 여전히 범위가 겹치는 포인터 전달 가능. 그 경우 정의되지 않은 결과
restrict를 사용할 때와 안 할 때의 차이
void increse(int* a, int* b, int* x)
{
*a += *x;
mov1 (%edx), $esi
add1 %esi, (%ecx)
*b += *x;
mov1 (%edx), $esi
add1 %esi, (%ecx)
}
void increse(int* restrict a, int* restrict b, int* restrict x)
{
*a += *x;
mov1 (%edx), $edx
add1 %edx, (%ecx)
*b += *x;
add1 %edx, (%ecx)
}
- 두 번째 코드 실행 시, x가 가리키는 값을 다시 레지스터에 읽어오지 않음
- 즉, 여기서는 어떤 포인터도 서로 같은 메모리를 가리키지 않을 것이라 가정
- 따라서 컴파일러 최적화를 할 수 있음
그러나.. 호출자는 여전히 무시할 수 있다
- restrict는 컴파일러에게 '안전 장치 꺼도 됨'이라고 말해주는 것
- 그러나 프로그래머는 여전히 무시할 수 있음
- 여전히 메모리 범위가 겹치는 포인터들을 전달할 수 있음
- 이 과목의 코딩표준 중 매개변수에 '_or_null'을 붙이는 때와 비슷
- 함수가 NULL 매개변수를 제대로 처리하는 경우만 그렇게 표시
- 그러나 그렇지 않은 매개변수에 NULL을 전달하는 걸 막을 수는 없음
- assert()가 유일한 안전 장치
restrict의 필요성
- 일부 하드웨어의 경우, 매우 빠르게 메모리 복사가 가능(ex. DMA)
- 이를 위해 #define을 통해 플랫폼 전용 memcpy() 등을 만듦
- 보통 이러한 함수들에는 여러가지 제약이 있음
- 가장 대표적인 게 메모리 범위가 겹치지 않아야 한다는 것(즉, restrict)
- 그 외의 제약으로는 메모리정렬(alignment)도 있음
restrict를 무시할 경우의 위험성
- inline의 경우, 함수가 인라인화가 안 돼도 큰 문제가 없었음
- 그냥 일반 함수가 호출됨
- 그러나 restrict 덕에 최적화 된 코드에 포인터를 잘못 넣으면?
- 어떻게 될 지 모름
- C99 표준이라 C에서는 많이 못 쓰지만 매우 중요한 개념
- C++에서도 매우 흔히 사용함
한 줄 주석
- // 사용 가능
변수 선언
- C99에서는 블록 중간에 변수 선언이 가능해짐
(복습) 가변 인자 함수
- 정해지지 않은 수의 매개변수(가변 인자)를 허용하는 함수
int add_ints(const size_t count, ...); int printf(const char *format, ...);
- 가변 인자 함수와 관련된 매크로 함수 세 가지를 배움
- va_start()
- 가변 인자에 접근하기 전에 반드시 호출하는 하뭇
- va_arg()
- 가변 인자 목록으로부터 다음 가변 인자를 가져오는 함수
- va_end()
- 가변 인자에 접근을 다 한 후에 반드시 호출하는 함수
- va_start()
va_copy()
va_copy(dest, src)
-
C99에 추가
-
가변 인자 목록을 복사하는 매크로 함수
-
dest를 다 사용한 후에는 반드시 va_end()를 호출해야
double get_variance(int count, ...) { va_list arg_list_avg; va_start(arg_list_avg, count); va_list arg_list_v; va_copy(arg_list_v, arg_list_avg); double avg = 0.0; for(size_t i = 0; i < count; ++i){ double num = va_arg(arg_list_avg, double); avg += num; } avg /= count; va_end(arg_list_avg); }
sprintf()의 문제점
int sprintf(char* buffer, const char* format, ...);
char buffer[20];
const char* name = "Caterina Hassinger";
int score = 100;
sprintf(buffer, "%s's score: %d\n", name, score);
- 안전하지 않음
- buffer의 크기보다 긴 문자열이 들어와도 중간에 멈추지 않음
- 즉, buffer 범위를 넘어서서 계속 씀
snprintf()
int snprintf(char* restrict buffer, size_t bufsz, const char* format, ...);
- 최대 bufsz-1개의 문자열을 출력
- 나머지 하나는 바로 널 문자 용
- 언제나 붙여준다!
- strncpy()와는 다르다!
- 하지만 실무에서는 마지막 요소에 널 문자 넣는 코드가 많이 보임
- strncpy() 처럼
#include <stdio.h>
#define LENGTH (20)
int main()
{
char buffer[LENGTH];
const char* name = "Caterina Hassigner";
int score = 100;
snprintf(buffer, LENGTH, "%s's score: %d\n", name, score);
return 0;
}
왜 널 문자를 넣어야 하나요?
- C89에서 자기만의 _snprintf()를 제공한 컴파일러가 있었음
- 이 함수는 널 문자를 안 붙여줬음
- 이제는 snprintf()를 만들어서 제대로 지원
- 그러나 여전히 호환 때문에 _snprintf()를 남겨둠
- 고로 안전을 위해 언제나 마지막 요소에 널 문자를 넣는 코드를 둠
snprintf(buffer, LENGTH, "%s's score: %d\n", name, score); buffer[LENGTH-1] = '\0';
- 사실 이 방법은 다음에 볼 예외 상황에서도 안전
snprintf()도 위험할 수 있다!
#define LENGTH (20)
char buffer[LENGTH];
snprintf(buffer, 0, "%s's score: $d\n", name, score);
- bufsz가 0이면 아무것도 안 함
- 즉, 아무것도 안 썼으니까 널 문자도 안 붙여줌
- 이 때, buffer을 읽으면 엄한 메모리까지 읽어올 수 있음
- '\0'을 만날 때까지 계속 읽음
- 이 밖에도, buffer나 format이 NULL이면 펑펑 터짐
long long int
- C89에서는 최소 64비트인 정수형은 없었음
- 그러나 C99에서는 생김
- 그것이 바로 long long int
- 최소 64비트이고 long이상의 크기
- 다른 언어에서는 보통 그냥 long
- 표준에 상관 없이 보통 안전하게 생각해도 되는 것
- int 생략 가능
long long int의 리터럴
부호 있는 경우
long long big_num1 = 34534275098;
long long int big_num2 = 7869889796ll;
부호 없는 경우
unsigned long long big_num3 = 34534275098ULL;
unsigned long long int big_num4 = 7869889796U;
long long int의 서식문자
long long big_num1 = 709879807897;
long long int big_num2 = 78097675678ULL;
printf("big_num1: %lli\n", big_num1);
printf("big_num1: %llu\n", big_num2);
- 부호 있는 경우 : %lli
- 부호 없는 경우 : %llu
불형
- 더 이상 #define TRUE (1) 안 해도 됨
- 두 가지 방법
- _Bool
- bool: 헤더 인클루드 필요
_Bool
- 거짓이면 0, 참이면 1
- char/int/float과 같은 값을 _Bool에 넣을 경우
- 0에 해당하는 값이면 0
- 그 외의 값은 1로 변환
- 여전히 참과 거짓은 숫자로 표현
bool, true, false
- <stdbool.h> 헤더에 정의(#define)되어 있음
- bool : _Bool을 다시 정의
- true : 1로 정의
- false : 0으로 정의
개발자들이 자체적으로 만든 bool과 충돌나기 때문에 bool을 기본으로 넣을 수가 없었음
typedef unsigned int bool;
#define TRUE (0)
#define FALSE (1)
//혹은
typedef enum {
false,
true
} bool;
같은 자료형인데 크기가 다름
- int 형을 32비트라 가정하고 코딩하면 16비트인 곳에서 문제
- 정수형의 크기가 고무줄이 아니면 됨
- 그러면 프로그램을 어느 플랫폼으로 포팅하더라도 큰 문제 없음
고정 폭 정수형
- <stdint.h> 헤더에 정의되어 있음
- int8_t / uint8_t
- int16_t / uint16_t
- int32_t / uint32_t
- in64_t / uint64_t
- C++에서도 뒤늦게 얘내 가져감
_Imaginary, _Complex
- _Imaginary
- 허수를 나타내는 키워드
- 일부 컴파일러는 지원 안 할 수 있음
- _Complex
- 복소수를 나타내는 키워드
- 일부 컴파일러는 다른 이름을 사용할 수 있음
- 각각 자료형이 세 개씩 존재
float _Imaginary double _Imaginary long long _Imaginary
float _Complex
double _Complex
long long _Complex
## <complex.h>
- 허수와 복소수와 관련 있는 헤더 파일
- _Bool과 마찬가지로 _Imaginary와 _Complex를 재정의한 매크로를제공
```c
float imaginary
double imaginary
long long imaginary
float complex
double complex
long long complex
- I
- 허수부에서 사용하는 i
- 허수, 복소수와 관련된 유틸리티 함수 제공
IEEE 754 부동 소수점 정식 지원
- C99의 주요 기능 중 하나
- float은 IEEE 754 32비트 부동 소수점
- double은 IEEE 754 64비트 부동 소수점
- long double은 IEEE 754 확장 정밀도(extended precision) 부동 소수점
- 사칙 연산과 제곱근의 올림을 IEEE 754에서 정의한대로 처리
'프로그래머 > C, C++' 카테고리의 다른 글
[포프 tv 복습] Type-Generic 함수 만들기, 정적 어서트, 메모리 정렬, 멀티스레딩 (0) | 2020.12.04 |
---|---|
[포프 tv 복습] C99, C11 (0) | 2020.12.03 |
[포프 tv 복습] 전처리기 (0) | 2020.11.30 |
[면접 대비] C를 사용한 해시 맵 구현 (0) | 2020.11.29 |
[면접 대비] C를 사용한 linked list 구현 (0) | 2020.11.29 |