Diagnostics
#error
와 #warning
지시자를 이용한 메시지는 stderr
로 출력됩니다.
#error
전처리 시에 fatal error 메시지를 출력할 때 사용하는 지시자 입니다.
지시자 이후에 오는 token 들은 메시지를 출력하는데 사용됩니다.
메시지에서 매크로 확장은 발생하지 않고 이어지는 spaces 들은 하나로 출력됩니다.
#error
지시자 위치에서 전처리가 종료되는 것은 아니고 끝까지 실행됩니다.
전처리기 종료 상태 값으로
1
이 반환되고 이어지는 컴파일 작업은 중단됩니다.
/usr/include/x86_64-linux-gnu/bits/stdio.h 참조
#ifndef _STDIO_H
# error "Never include <bits/stdio.h> directly; use <stdio.h> instead."
#endif
-------------------------------------------
/usr/include/x86_64-linux-gnu/sys/elf.h 참조
#ifdef __x86_64__
# error This header is unsupported on x86-64.
#else
# warning "This header is obsolete; use <sys/procfs.h> instead."
#warning
전처리 시에 warning 메시지를 출력할 때 사용하는 지시자 입니다.
전처리기 종료 상태 값은
0
이되고 컴파일 작업은 정상적으로 진행됩니다.
/usr/include/math.h 참조
#if defined log && defined __GNUC__
# warning A macro called log was already defined when <math.h> was included.
# warning This will cause compilation problems.
#endif
-------------------------------------------------
/usr/include/features.h 참조
#if (defined _BSD_SOURCE || defined _SVID_SOURCE) && !defined _DEFAULT_SOURCE
# warning "_BSD_SOURCE and _SVID_SOURCE are deprecated, use _DEFAULT_SOURCE"
# undef _DEFAULT_SOURCE
# define _DEFAULT_SOURCE 1
#endif
기본적으로 시스템 헤더 에서 발생하는 warning 은 출력되지 않습니다.
아래 첫 번째 예제를 보면 <stdio.h>
에는 BUFSIZ
매크로가 정의되어 있는데
동일한 매크로를 다른 값으로 정의하였으나 redefined warning 이 발생하지 않고 있습니다.
두 번째와 같이 #define 을 시스템 헤더 아래로 내려야 기존과 같이 warning 이 발생합니다.
시스템 헤더에서 직접 #warning 지시자에 의한 메시지는 출력됩니다
$ gcpp > /dev/null
#define BUFSIZ 1234 // 또는 #include "myheader.h"
#include <stdio.h> // stdio.h 에 동일한 BUFSIZ 매크로가 정의되어
// 있지만 redefined warning 이 발생하지 않는다.
BUFSIZ
@
# empty
...........................................................................
$ gcpp > /dev/null
#include <stdio.h> // #define 을 시스템 헤더 아래로 내려야
#define BUFSIZ 1234 // 기존과 같이 warning 이 발생합니다.
BUFSIZ
@
<stdin>:2: warning: "BUFSIZ" redefined
In file included from <stdin>:1:
/usr/include/stdio.h:99: note: this is the location of the previous definition
99 | #define BUFSIZ 8192
|
Quiz
#include <signal.h>
할 경우 sigset_t
타입을 사용할 수 있는지 알려면 어떻게 할까요?
cpp 명령의 -dD
옵션을 이용하면 특정 헤더 파일이 #include 됐을 때
정의되는 매크로, 함수, typedef 정보들을 모두 볼 수가 있습니다.
하나의 헤더 파일이 #include 되면 실제로는 연관된 여러 개의 헤더 파일이 #include 되는데
이때 어떤 헤더 파일에 정의가 존재하는지도 알 수 있습니다.
다음을 보면 #include <signal.h>
할 경우 sigset_t
타입이 정의되어 사용할 수 있고,
실제 정의가 존재하는 파일은 /bits/types/sigset_t.h
인 것을 알 수 있습니다.
bash$ cpp -dD <<< '#include <signal.h>' | grep -w -B5 'sigset_t;'
unsigned long int __val[(1024 / (8 * sizeof (unsigned long int)))];
} __sigset_t;
# 5 "/usr/include/x86_64-linux-gnu/bits/types/sigset_t.h" 2 3 4 <---- 실제 정의가
존재하는 파일
typedef __sigset_t sigset_t; <---- sigset_t 타입이 존재한다.
bash$ cpp -dD <<< '#include <signal.h>' | grep -w -B5 '__sigset_t;'
#define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
typedef struct
{
unsigned long int __val[(1024 / (8 * sizeof (unsigned long int)))];
} __sigset_t;
#include <signal.h>
할 경우 sigmask
매크로를 사용할 수 있고
실제 정의는 signal.h
파일의 167 번 라인에 존재하는 것을 알 수 있습니다.
bash$ cpp -dD <<< '#include <signal.h>' | grep -w -B3 '#define sigmask'
extern void psiginfo (const siginfo_t *__pinfo, const char *__s);
# 167 "/usr/include/signal.h" 3 4 <---- signal.h 파일의 167 번 라인
#define sigmask(sig) ((int)(1u << ((sig) - 1))) <---- sigmask 매크로가 존재 한다.
매크로의 경우 확인할 수 있는 또 다른 방법은 다음과 같이 재정의 했을 때 warning 이 발생하면 정의되어 있는 것입니다. 이때도 매크로가 정의되어 있는 헤더 파일과 위치가 함께 표시됩니다.
# signal.h 헤더에 sigmask 매크로가 정의되어 있는지 확인
bash$ cpp <<< $'#include <signal.h>\n#define sigmask hello' > /dev/null
<stdin>:2: warning: "sigmask" redefined
In file included from <stdin>:1:
/usr/include/signal.h:167: note: this is the location of the previous definition
167 | # define sigmask(sig) ((int)(1u << ((sig) - 1))) <---- 정의되어 있다.
|
# 다음과 같이 정의되어 있다고만 나오면 predefined 매크로입니다.
bash$ cpp <<< $'#include <signal.h>\n#define __INTPTR_TYPE__ hello' > /dev/null
<stdin>:2: warning: "__INTPTR_TYPE__" redefined
<built-in>: note: this is the location of the previous definition
bash$ cpp -dM /dev/null | grep '__INTPTR_TYPE__'
#define __INTPTR_TYPE__ long int
#include <stdio.h>
했을 때 참조되는 모든 헤더 파일을 보려면 -M
옵션을 사용하면 됩니다.
bash$ cpp -M <<< "#include <stdio.h>"
-: /usr/include/stdc-predef.h /usr/include/stdio.h \
/usr/include/x86_64-linux-gnu/bits/libc-header-start.h \
/usr/include/features.h /usr/include/x86_64-linux-gnu/sys/cdefs.h \
/usr/include/x86_64-linux-gnu/bits/wordsize.h \
/usr/include/x86_64-linux-gnu/bits/long-double.h \
/usr/include/x86_64-linux-gnu/gnu/stubs.h \
. . .
. . .
2 .
다음은 shell 함수를 활용하여 특정 헤더를 #include 했을 때 정의되어 있는 함수, 매크로, typedef, struct, enum 을 출력하는 것입니다. 헤더가 포함된 소스코드를 붙여넣기 하여 확장된 결과를 출력해 볼 수도 있습니다.
# stdio.h, math.h 헤더를 #include 했을 때 정의되어 있는 함수를 출력 (함수형 매크로 포함)
# 기본적으로 이름 앞에 "_" 가 붙는 것은 제외하는데 포함시키려면 "-a" 옵션을 추가 하면 됩니다.
$ header-function stdio.h math.h
$ header-function <<\@ # heredoc 을 사용할 수도 있는데 이때는
#include <stdio.h> # #include 지시자가 포함되어야 합니다.
#include <math.h>
@
# header-function 명령의 경우 "-t" 옵션을 추가하면 __attribute__ 도 출력됩니다.
# sys/select.h 헤더를 #include 했을 때 'FD_SET(foo, &bar)' 매크로 확장 결과를 출력
$ header-macro sys/select.h -e 'FD_SET(foo, &bar)'
((void) (((&bar)->__fds_bits)[((foo) / (8 * (int) sizeof (__fd_mask)))]
|= ((__fd_mask) (1UL << ((foo) % (8 * (int) sizeof (__fd_mask)))))))
# 다음과 같이 하면 server.c 소스파일 전체의 매크로 확장 결과를 볼 수 있습니다.
$ header-macro < server.c
$ cat server.c | header-macro
# 다음과 같이 heredoc 을 이용해 코드를 붙여넣기 하여 확장 결과를 볼 수도 있습니다.
$ header-macro <<\@
#include <stdio.h>
#include <stdlib.h>
#include <sys/file.h>
int main() {
char buf[20];
FILE *file = fopen("tmp.txt", "r+");
flock( fileno(file), LOCK_EX);
for (int i = 1; i <= 100000; i++) {
fseek( file, 0, SEEK_SET);
fgets( buf, sizeof buf, file);
long int num = strtol( buf, NULL, 10) + 1;
fseek( file, 0, SEEK_SET);
fprintf( file, "%ld\n", num);
}
flock( fileno(file), LOCK_UN);
}
@
# 아무런 헤더를 주지 않으면 predefined macros 가 출력됩니다.
$ header-macro
# signal.h 헤더를 #include 했을 때 sigevent_t 타입 정의를 검색 (-s 옵션)
$ header-typedef signal.h -s sigevent
$ header-typedef <<\@ -s sigevent
#include <signal.h>
@
# header-struct 은 struct 과 union 을 출력합니다.
$ header-struct sys/socket.h
# header-dump 는 별도의 필터링 없이 전체 확장 결과를 출력합니다.
$ header-dump stdio.h
# header-dump 에서 -l 옵션은 stdio.h 헤더가 참조하는 모든 헤더 목록을 출력합니다.
$ header-dump -l stdio.h
# 기타 cpp 옵션을 추가하려면 마지막에 '--' 붙이고 추가하면 됩니다.
$ header-dump stdlib.h -- -D_GNU_SOURCE
$ header-struct foo.h -s bar -- -I . -I include
$ header-macro -- -I . -I include < foo.c
함수 사용을 위해선 clang-format
명령이 있어야 합니다.
function header-dump ()
{
local hsum opts hlist=false clang_format
_header-args "$@" || return
eval set -- $opts
if $hlist; then
cpp -M "$@" <<< $hsum
else
cpp -P -dD "$@" <<< $hsum
fi
}
function _header-format ()
{
sed -En -e '/\a|^#pragma/d' \
-e '/^\{$/{ h; :X n; H; /^}/{ g; /}$/{ s/.*/;/p; b}; p; b}; bX}' \
-e 'p' | eval "$clang_format"
}
function header-function ()
{
local hsum opts search all=false allm attr=false clang_format
_header-args "$@" || return
eval set -- $opts
if ! $all; then
all='\b[[:alpha:]]'
allm='/^#define _/d;'
else
unset all allm
fi
if ! $attr; then
set -- '-D__attribute__(forRemoving)=' '-D__asm__(forRemoving)=' "$@"
attr=
else
attr=' s/__\(/@/g'
fi
test -n "$search" && search='\w*'$search'\w*' || search='\w+'
cpp -P "$@" <<< $hsum | _header-format |
sed -En -e '/^(__extension__ |extern )?(typedef|struct|union|enum|extern) [^()]*[;{]$/ {' \
-e '/;$/d' -e '/\{$/{ :X n; /^}/b; bX' -e '}}' \
-e '/^\{$/{ :Y n; /^};?$/{ s//;/p; b}; bY }' -e 'p' |
sed -zE 's/(\s*;)+/;\n/g' |
sed -En -e '/^$/!H; /;$/ { g; s/\bsizeof\(//g; '"$attr" \
-e '/'"$all"'\w+\(\s*\w/!bX; /__typeof__\(/bX;' \
-e '/'"$search"'\(/I{ /\btypedef /!{ g; p }}; :X z; h' \
-e '}' | eval "$clang_format"
echo
cpp -dD "$@" <<< $hsum |
sed -En -e "$allm"' /^#define '"$search"'\(/I{ s/$/\n/p }' | eval "$clang_format"
}
function header-macro ()
{
local hsum opts search expand all=false clang_format
_header-args "$@" || return
eval set -- $opts
! $all && all='/^#define _/d;' || unset all
test -n "$search" && search='\w*'$search'\w*' || search='\w+'
if test -n "$hsum"; then
if test -n "$expand" ; then
cpp -dI -P "$@" <<< $hsum$'\n'$expand$'\n' | sed '/\a/,/\a/d'
else
cpp -dD "$@" <<< $hsum |
sed -En "$all"' /^#define '"$search"'/Ip' | sort
fi
else
cpp -dM "$@" /dev/null | sed -En '/^#define '"$search"'/Ip' | sort
fi | eval "$clang_format"
}
function header-enum ()
{
local hsum opts search clang_format
_header-args "$@" || return
eval set -- $opts
test -n "$search" && search='/'$search'/I!b;'
cpp -P "$@" <<< $hsum | _header-format |
sed -En -e '/^(typedef )?enum .*\{/ { h ' \
-e '/;$/ bZ' -e ':X n; H; /^}/{ g; bZ }; bX' \
-e ':Z '"$search"' s/$/\n/p }'
}
function header-typedef ()
{
local hsum opts all=false all1 all2 search r clang_format
_header-args "$@" || return
eval set -- $opts
r='([][[:alnum:]_]|[ ]*,[ ]*|[*][ ]*)'
if ! $all; then
all1='/\b[[:alpha:]]'$r*';$/!b;'
all2='/\b[[:alpha:]]'$r*'\)?\(/!b;'
fi
test -n "$search" && search=$r*$search$r* || search=$r*
cpp -P "$@" <<< $hsum | _header-format |
sed -En -e '/^(__extension__ )?typedef /{' \
-e '/\{$/ { h; :X n; H; /^}/{ g; bZ1 }; bX }' \
-e '/;$/ { h; /__attribute__/bZ1; /\(/bZ2; bZ1 }' \
-e 'h; :Y n; H; /;$/{ g; /__attribute__/bZ1; bZ2 }; bY' \
-e ':Z1 s/\s*__attribute__.*\)\)//; '"$all1"' /'"$search"';$/I{ g; s/$/\n/p }; b' \
-e ':Z2 '"$all2"' /'"$search"'\)?\(/I{ s/$/\n/p }; b' \
-e '}'
}
function header-struct ()
{
local hsum opts search all=false clang_format
_header-args "$@" || return
eval set -- $opts
! $all && all='/^(typedef )?(struct|union) [a-zA-Z]/!b;' || unset all
test -n "$search" && search='\w*'$search'\w*' || search='\w+'
cpp -P "$@" <<< $hsum | _header-format |
sed -En -e '/^(typedef )?(struct|union) .*\{/ {' \
-e '/;$/ bY' -e 'h; :X n; H; /^}/{ g; bY }; bX;' \
-e ':Y '"$all"' /^(typedef )?(struct|union) '"$search"' \{/I{ s/$/\n/p }' \
-e '}'
}
function _header-args ()
{
local line
clang_format='clang-format -style="{ AllowShortFunctionsOnASingleLine: None,
IndentWidth: 4, BraceWrapping: { AfterFunction: true }, BreakBeforeBraces: Custom }"'
if test $# -gt 0; then
options=$(getopt -o s:e:lat -- "$@") || return
eval set -- $options
while true; do
case $1 in
-s) # 검색 옵션
[ "${FUNCNAME[1]}" != "header-dump" ] && search=$2
shift 2 ;;
-e) # header-macro 확장 옵션
[ "${FUNCNAME[1]}" = "header-macro" ] && expand=$2
shift 2 ;;
-a) # underline 이름까지 출력
[ "${FUNCNAME[1]}" != "header-enum" ] && all=true
shift ;;
-t) # header-function 에서 __attribute__ 도 출력
[ "${FUNCNAME[1]}" = "header-function" ] && attr=true
shift ;;
-l) # header-dump 에서 header 목록 출력
[ "${FUNCNAME[1]}" = "header-dump" ] && hlist=true
shift ;;
--)
shift
while [[ $1 =~ ([<\"])?([[:alnum:]_/.-]+\.[hc])([\">])? ]]; do
if [ -n "${BASH_REMATCH[1]}" ]; then
hsum+=$'\n\a\n#include '${BASH_REMATCH[1]}${BASH_REMATCH[2]}${BASH_REMATCH[3]}$'\n\a\n'
else
hsum+=$'\n\a\n#include <'${BASH_REMATCH[2]}$'>\n\a\n'
fi
shift
done
break
esac
done
if [ $# -gt 0 ]; then opts=`printf "%q " "$@"`; fi
fi
if ! test -t 0; then
while IFS= read -r line; do
if [[ $line =~ ^[[:blank:]]*\#[[:blank:]]*include ]]; then
hsum+=$'\n\a\n'$line$'\n\a\n'
else
hsum+=$line$'\n'
if [[ "${FUNCNAME[1]}" = "header-macro" &&
-z $expand && ! $line =~ ^[[:blank:]]*$|^[[:blank:]]*\# ]]
then expand=$'\n'; fi
fi
done
fi
}
다음은 위에서 작성한 명령들을 사용할때 헤더이름 입력을 위한 자동완성 함수 입니다.
glob 문자를 이용한 검색도 가능하고 ( *foo*[tab]
), 검색 후 번호를 이용한 입력도 됩니다 ( 15[tab]
)
_header_comp_bind() { bind '"\011": complete' ;}
_header_comp_search()
{
local res count opt
_header_comp_number=""
words=$( <<< $words sort -u )
local IFS=$'\n'; echo
for v in $words; do
if [[ $v == $cur ]]; then
res+=$'\e[36m'"$v"$'\e[0m\n'
let count++
_header_comp_number+="$count $v"$'\n'
fi
done
(( count >= LINES )) && opt="+Gg"
less -FRSXiN $opt <<< ${res%$'\n'}
bind -x '"\011": _header_comp_bind'
}
_header_comp()
{
local IFS=$' \t\n' cmd=$1 cur=${COMP_WORDS[COMP_CWORD]} words dirs i
COMP_LINE=${COMP_LINE:0:$COMP_POINT}
[[ ${COMP_LINE: -1} = " " ]] && cur=""
for (( i = 1; i < ${#COMP_WORDS[@]}; i++ )); do
[[ ${COMP_WORDS[i]} == "--" ]] && break
done
if (( i < ${#COMP_WORDS[@]} )); then
for (( i = i + 1; i < ${#COMP_WORDS[@]}; i++ )); do
if [[ ${COMP_WORDS[i]} == -I ]]; then
[[ -d ${COMP_WORDS[++i]} ]] && dirs+=${COMP_WORDS[i]}$'\n'
elif [[ ${COMP_WORDS[i]} == -I* ]]; then
[[ -d ${COMP_WORDS[i]#-I} ]] && dirs+=${COMP_WORDS[i]#-I}$'\n'
fi
done
fi
dirs+=$(
if [[ $cmd == *k ]]; then
declare -f _header-kernel | sed -En 's/ (-I[ ]*[^ ]+)/\n\1/g; T; :X /^-I/{ s/^-I[ ]*//; P}; s/.*\n?//M1; /[[:alpha:]]/bX'
else
gcc -E -xc -Wp,-v /dev/null |& sed -En '/#include <...> search/,/End of search/{ //d; p }'
fi
)
words=$( <<< $dirs xargs -i sh -c 'cd {} && find -name "*.h" -printf "%P\n"' )
if [[ $cur == +([0-9]) ]]; then
words=$( <<< $_header_comp_number awk '$1 == '"$cur"' { print $2; exit }' )
COMPREPLY=( "$words" )
elif [[ $cur == *[[*?]* ]]; then
_header_comp_search
else
COMPREPLY=($(compgen -W "$words" -- "$cur"))
fi
}
complete -F _header_comp header-{macro,function,struct,typedef,enum,dump}{,k}
3 .
보통 release 빌드 시에는 컴파일러에 -D NDEBUG
옵션을 주어 코드 내에서 사용중인
assert 매크로를 제거합니다.
그런데 assert 매크로 정의를 살펴보면 empty 값이 아니라 ((void)0)
가 사용되는 것을
볼 수 있는데요. 왜 그럴까요?
$ header-dump assert.h | grep '#define assert'
#define assert(expr) ((void) sizeof ((expr) ? 1 : 0), __extension__ ({ if (expr) ; else __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION); }))
$ header-macro <<\@
#include <assert.h>
assert(1 == 1)
@
((void)sizeof((1 == 1) ? 1 : 0), __extension__({
if (1 == 1)
;
else
__assert_fail("1 == 1", "<stdin>", 5,
__extension__ __PRETTY_FUNCTION__);
}))
-------------- -D NDEBUG 옵션을 이용한 release 빌드시 --------------
$ header-dump assert.h -- -D NDEBUG | grep '#define assert'
#define assert(expr) (__ASSERT_VOID_CAST (0))
$ header-macro -- -D NDEBUG <<\@
#include <assert.h>
assert(1 == 1)
@
((void)(0))
C/C++ 에서는 ,
operator 가 있어서 expression 을 연이어서 쓸수가 있습니다.
만약에 -D NDEBUG
옵션을 설정했을 때 assert 매크로를 empty 로 설정한다면
아래 두 번째와 같은 결과가 되어 문법상 오류가 됩니다.
따라서 아무 기능도 하지 않는 #define assert(expr) ((void)(0))
를 사용합니다.
i = 20, j = 2 * i, assert(i == j);
i = 20, j = 2 * i, ; # 문법상 오류가 된다.
i = 20, j = 2 * i, ((void)(0)); # 오류가 되지 않는다.
int main()
{ # (void) 를 붙이는 이유는
0; # 이것은 warning 이 된다. (Expression result unused)
(void)0; # OK
}
4 .
stdatomic.h
헤더를 보면 아래와 같이 __typeof__
에 (void)0
가 사용된 것을
볼 수 있습니다. 여기서 (void)0
는 아무 기능도 하지 않기 때문에 없어도 될것 같은데
왜 포함되었을까요?
#define atomic_store_explicit(PTR, VAL, MO) \
__extension__ \
({ \
__auto_type __atomic_store_ptr = (PTR); \
__typeof__ ((void)0, *__atomic_store_ptr) __atomic_store_tmp = (VAL); \
__atomic_store (__atomic_store_ptr, &__atomic_store_tmp, (MO)); \
})
__typeof__ ((void)0, *__atomic_store_ptr)
에서 사용된 (void)0
는 ,
operator 를
사용하기 위해 추가된 것입니다.
,
operator 에서 사용된 식은 ( 여기서는 *__atomic_store_ptr
가 되겠죠 )
L-value conversion 대상이 됩니다.
예를 들어 ((void)0, X)
에서 X 의 타입이 int[3]
배열이라면 int*
로
변경이 되고, int(void)
와 같은 함수라면 int(*)(void)
로 변경이 됩니다.
또한 volatile
, const
or _Atomic
같은 한정자도 제거가 됩니다.
void foo( int arr[3], int fun(void) ) { ... }
와 같은 함수가 있다면 함수의 본문 내에서 arr 는 타입이int*
가 되고 fun 는int(*)(void)
가 되는것을 생각하면 됩니다.
int arr[3];
int (*ptr)[3];
atomic_store_explicit(ptr, &arr, XXX);
// 여기서 __atomic_store_ptr 의 타입은 (ptr) 값에 따라 int(*)[3] 이 됩니다.
__auto_type __atomic_store_ptr = (ptr);
// 만약에 다음과 같이 (void)0, 를 사용하지 않는다면 *__atomic_store_ptr 은
// dereferencing 에 의해 __typeof__ (*__atomic_store_ptr) 의 타입은 int[3] 이 되게 됩니다.
__typeof__ (*__atomic_store_ptr) __atomic_store_tmp = (VAL);
// 이것은 결과적으로 다음과 같은 문장이 되므로 컴파일 오류가 됩니다.
int __atomic_store_tmp[3] = arr;
// 대신에 (void)0, 를 추가해서 "," 연산자를 사용하게 되면
__typeof__ ((void)0, *__atomic_store_ptr) __atomic_store_tmp = (VAL);
// L-value conversion 이 되므로 다음과 같은 결과가 되어 오류가 발생하지 않게 됩니다.
int *__atomic_store_tmp = arr;