2023. 1. 7. 21:00ㆍC언어 복습
1. restrict
이번 키워드도 C99부터 추가된 기능이다. restrict는 어떤 범위를 제한한다는 뜻을 가지고 있다.
그럼 실제로 C에서 restrict는 어떤 기능일까? 강의에서는 문자열 복사에 대한 상황을 예시로 들면서 restrict에 대해서 설명했다. 다들 C언어를 배우면서 문자열을 복사하는 함수를 만들어본 경험이 있는가? 만들어본 적은 없어도 사용해본 적은 있을 것이다. C에서 문자열 복사 함수를 사용할 때 혹시 아래의 그림과 같은 생각을 해본 적이 있는가?
문자열 함수는 src와 dest를 매개 변수로 받는데 인자로 그림과 같이 전달하면 어떻게 될까? 아래의 두 개의 그림 중 하나의 결론이 나올 것이라고 생각할 수 있다.
1. 문자열이 그대로 덮어 써진다.
2. 원래 문자열이 그대로 복사된다.
정답은... "결과가 정의되어 있지 않다." 가 정답이다.
즉, 크래시가 나도 이상할 것이 없는 것이다. 근데 사실 생각을 해보면 위의 가정 자체가 말이 안된다. 아니, 어떤 사람이 문자열 복사를 하는 함수에서 저런 식으로 코드를 짠다는 말인가? 하지만 우리는 호출자의 행동을 강제할 수 있는 방법이 없다. 그래서 컴파일러들은 이것과 비슷한 상황에서 좀 더 안전하게 방어적으로 구현을 하는 경우가 많다. 하지만 이런 방어적인 구현은 성능의 저하로 이어질 수 밖에 없는 것이 결국 조건문을 더 필요로 하기 때문이다. 그럼 여기서 이런 의문이 들 수 있다.
"C가 위험한 대신에 성능을 보고 쓰는 언어인데 이런 이유로 성능을 잃는 것이 맞는 것인가?"
여기서 사용하는 것이 restrict라는 키워드이다. 이 키워드는 포인터에 사용하는 키워드로 컴파일러에게 "이 포인터가 가리키는 데이터의 메모리는 절대 다른 변수와 겹치지 않는다." 라는 이야기를 하는 것이다. 그러나 컴파일러는 이 이야기를 무시하고 restrict에 대한 처리를 하지 않을 수 있다. 여기서 알아야 할 것은 메모리가 다른 변수와 겹치지 않을 것을 강제하는 키워드가 아니라는 것이다. 여전히 다른 변수와 메모리 공간이 겹칠 수 있지만 이런 경우에는 정의되지 않는 결과가 나온다.
참고로 restrict를 사용했을 때와 사용하지 않았을 때의 차이는 최적화에 있다. 원래 컴파일러가 C에서 어셈블리어로 변환을 시킬 때에는 메모리가 다른 변수와 겹칠 수 있는 상황에 대비해서 변환을 시키는데 restrict를 사용하면 그런 가정을 하지 않아도 된다고 알려주면서 가장 빠른 성능으로 어셈블리어로 변환할 수 있게끔 해준다. 또한 restrict를 사용하는 것에는 다음과 같은 의미도 담겨있다.
"이곳에는 다른 변수와 메모리가 겹치는 포인터가 매개 변수로 전달되면 안됩니다. 만약 그런 행위를 하다가 문제가 생긴다면 호출자의 잘못입니다."
이것은 매개 변수로 NULL이 전달될 수 있을 때, 매개 변수의 이름에 _or_null과 같은 문구를 추가해주는 것과 같은 맥락이라고 보면 된다. 다만 C에서는 C89에서는 지원이 되지 않기 때문에 자주 쓰기 꺼려질 수 있을지 몰라도 C++에서는 자주 쓰이기 때문에 알아두면 좋다.
2. C99 기능
이 부분은 특별히 길게 다룰 부분은 없고 그냥 C99부터 이런 것들이 추가되었고 그게 무엇인지는 정말 간단하게만 짚고 넘어갈 것이다. 마치 아주 간단한 패치 노트 요약본처럼 말이다. 하나하나 자세하게 들여다보고 외울 필요가 없다고 느꼈고 그냥 '아, 이런 것이 있구나' 하고 넘어가는 부분이라고 판단했다.
실제로 사용이 가능한 것들
- C99부터 //를 이용해서 한 줄 주석이 가능하다.
- C99부터 굳이 맨 위에 변수 선언을 몰아넣지 않아도 된다.
- va_copy() - 이 함수는 가변 인자 함수에서 사용했던 va_list를 다른 va_llist에 복사하는 함수이다.
- snprintf() - 기존 문자열 함수에서 다뤘던 sprintf에서 좀 더 안전하게 char형 배열의 size를 받는 함수이다. 단, 이 함수는 끝에 널 문자를 넣음에도 불구하고 맨 끝에 널 문자를 넣는 코드를 보는 경우가 있는데 이는 두 가지 이유 때문에 그런 것이다.
- 인자로 size가 0이 전달되었을 경우 - 이러면 snprintf는 아무것도 하지 않아서 널 문자없이 쓰레기 값이 그대로 들어있는 상태가 될 수 있다.
- C89에는 snprintf가 아니라 _snprintf라는 함수를 컴파일러마다 자체적으로 지원했는데 _snprintf는 맨 끝에 널 문자를 붙이지 않아서 호환성 때문에 붙일 수 있다.
- long long int - 최소 64비트이며 long 이상의 크기이다. 이에 맞는 리터럴을 위한 접미사와 서식 문자도 추가되었다.
- 참, 거짓을 위한 자료형인 _Bool, bool이 추가되었다. 단, bool을 쓰려면 <stdbool.h>를 인클루드해야 한다. 기존에 많은 개발자들이 bool을 자체적으로 구현했었기 때문에 이름 충돌이 나는 것 때문에 이렇게 제공하는 것이라고 한다.
- 고정폭 정수형이 지원된다. 이제 int를 쓸 때, int가 이 기기에서 32비트일지 16비트일지와 같은 고민을 하지 않고 고정폭 정수형 자료형을 사용하면 된다. (int8_t, int16_t 등등이 있고 <stdint.h>를 인클루드 해야한다.)
- 허수를 표현하는 자료형이 추가되었고 <complex.h>를 인클루드 해야한다. 지원을 제대로 안할 수 있다고 한다.
- C89에서는 IEEE 754가 표준이 아니었는데 이번에 C99에서 표준으로 들어왔다.
- C89에서 C99로 넘어오면서 부동소수점 예외 처리가 좀 더 세분화되었다.
- Generic Type의 수학 관련 함수가 추가되었고 정수와 실수뿐만 아니라 복소수도 가능하다.
- 매개 변수로 배열을 넘길 때 int arr[ ]과 같이 넘기는 경우가 있는데 내부적으로 int* arr로 작동을 한다. 이 때, 기존에는 이것을 int* const arr; 과 같이 한정자를 넣을 방법이 없었는데 이제 [ ] 안에 int arr[const restrict]와 같이 한정자를 넣을 수 있게 되었다. static으로 배열의 길이를 명시해서 컴파일러에게 최적화를 요구할 수도 있다.
- 전에 가변 인자 함수에 대해서 배운 적이 있을 것이다. 그런데 C99부터는 매크로 함수도 가변 인자 함수로 작성할 수 있다. 단, 이런 경우에는 가변 인자들을 다른 어떤 함수에 전달하는 목적으로만 사용이 가능하며 __VA_ARGS__로 가변 인자 목록을 사용할 수 있다. #define FUNC(...) func(__VA_ARGS__) 와 같은 방식이다.
사실상 잘 사용되지 않는 것들
- 복합 리터럴이라는 것을 지원하는데 여러 개의 데이터를 어떤 복잡한 자료형에 넣어서 잠깐 만들어서 쓰고 버리고 싶을 때 사용한다. 예를 들면 struct foo({ 1.0f, "aabbcc" }); 와 같은 방식이다. 배열에도 똑같이 사용이 가능한데 이걸 쓰는 이유는 보통 코드의 줄 수를 줄이기 위해서일 것이다. 하지만 코드의 가독성을 해치기 때문에 베스트 프랙티스는 그냥 쓰지 않는 것이라고 한다.
- int arr[n]; 과 같이 가변 길이 배열을 선언할 수 있게 되었으나 C11에서 구현 사항을 선택으로 만들었고 2018년에 리눅스 커널에서 빠졌다. 메모리 관리 측면에서 함수의 스택 프레임을 잡을 때, 표준상으로 메모리가 어디에 어떻게 잡힐지 몰라서 많은 비판을 받고 빠진 것이다.
'C언어 복습' 카테고리의 다른 글
POCU C언어 정주행 19회차 - C11 유니코드, 베스트 프랙티스 (0) | 2023.01.12 |
---|---|
POCU C언어 정주행 18회차 - 멀티바이트, wchar_t (0) | 2023.01.09 |
POCU C언어 정주행 16회차 - inline 함수, inline 주의점, 해결 방법 (1) | 2023.01.07 |
POCU C언어 정주행 15회차 - 어서트 재정의, 전처리 명령어와 장단점 (0) | 2023.01.05 |
POCU C언어 정주행 14회차 - malloc과 free, 메모리 함수, 메모리 관리 기법 (0) | 2022.12.31 |