Command-line options
-D , -U
명령 라인에서도 -D
옵션을 이용해 매크로를 정의할 수 있습니다.
=
를 이용해 값을 따로 정의하지 않으면 1
로 설정됩니다.
-U
옵션은 predefined 매크로나 이전에 -D
옵션으로 정의한 매크로를
undef 할 수 있습니다.
$ gcpp -D __foo -D __bar=200
#ifdef __foo
"__foo" is : __foo
"__bar" is : __bar
#endif
@
"__foo" is : 1 # __foo 값은 1 이 된다.
"__bar" is : 200
-include file
-imacros file
이 옵션을 사용하면 소스파일의 제일위에 #include "file"
을 넣는 것과 같게됩니다.
한 가지 차이점은 파일을 찾을 때 소스파일이 위치한 곳에서 찾지 않고
현재 전처리기가 실행 중인 디렉토리에서 찾습니다.
-imacros
옵션은 매크로만 include 됩니다.
-I dir
-iquote dir
-isystem dir
-idirafter dir
#include
지시자에서 파일 이름은 #include <filename>
와 #include "filename"
두 가지 형태로 작성할 수 있습니다.
첫 번째를 시스템 헤더라고 하고 기존에 gcc 에 설정되어 있는 시스템 디렉토리를
이용해 상대 경로로 간단히 표기할 수 있습니다.
두 번째 quoted 형태의 경우는 파일을 찾을 때 현재 소스파일과 같은 디렉토리에서 찾게 됩니다.
gcc 에 기본적으로 설정되어 있는 시스템 디렉토리는 다음 명령으로 조회해볼 수 있습니다.
$ gcc -E -xc -Wp,-v /dev/null |& grep '^\s*/' # C
/usr/lib/gcc/x86_64-linux-gnu/10/include
/usr/local/include
/usr/include/x86_64-linux-gnu
/usr/include
$ gcc -E -xc++ -Wp,-v /dev/null |& grep '^\s*/' # C++
/usr/include/c++/10
/usr/include/x86_64-linux-gnu/c++/10
/usr/include/c++/10/backward
/usr/lib/gcc/x86_64-linux-gnu/10/include
/usr/local/include
/usr/include/x86_64-linux-gnu
/usr/include
--------------------------------------------------
$ ls /usr/include/stdio.h
/usr/include/stdio.h
$ ls /usr/include/x86_64-linux-gnu/sys/socket.h
/usr/include/x86_64-linux-gnu/sys/socket.h
#include <stdio.h> # 시스템 디렉토리 상대 경로로 간단히 표기할 수 있다.
#include <sys/socket.h>
헤더 파일을 찾는 순서
기본적으로 옵션은 같은 옵션을 여러 번 중복해서 사용할 수가 있고
left-to-right 순으로 찾게 됩니다.
-iquote
옵션은 #include "filename"
형태에만 적용이 되고
-I
, -isystem
, -idirafter
옵션의 경우는 #include <filename>
, #include "filename"
두 가지 형태에 모두 적용됩니다.
소스파일을 작성할 때 설정한
#include "filename"
파일을 제일 먼저 찾습니다.
( 현재 소스파일과 같은 디렉토리에서 찾게 됩니다. )명령 라인에서
-iquote dirname
옵션으로 설정한 디렉토리에서 파일을 찾습니다.명령 라인에서
-I dirname
옵션으로 설정한 디렉토리에서 찾습니다.명령 라인에서
-isystem dirname
옵션으로 설정한 디렉토리에서 찾습니다.gcc 에 기본적으로 설정되어 있는 시스템 디렉토리에서 찾습니다.
마지막으로 명령 라인에서
-idirafter dirname
옵션으로 설정한 디렉토리에서 찾습니다.
위에서 순서를 보면 시스템 디렉토리 보다 -I
옵션으로 설정한 디렉토리를 먼저 찾게 되므로
-I
옵션을 이용하면 시스템 헤더 파일을 override 할 수도 있습니다.
( vendor-supplied 시스템 헤더 파일을 설정할 때는 -isystem
옵션을 사용해야 합니다 ).
시스템 헤더 파일은 다르게 처리됩니다.
리눅스 같은 운영체제나 gcc 의 runtime libraries 에서 사용되는 헤더 파일의 경우는 strictly conforming C 하게 작성할 수가 없다고 합니다. 따라서 gcc 는 시스템 헤더 를 특별히 취급해서 헤더 파일을 프로세싱 할때 발생하는 모든 warning 을 suppress 한다고 합니다.
-isystem
와 -idirafter
옵션으로 설정한 디렉토리는 시스템 디렉토리로 간주되므로
시스템 디렉토리와 동일하게 처리됩니다.
다음을 보면 리눅스 소스에서 linux/jiffies.h
헤더 파일을 매크로 확장하려고 한 것인데
-I
옵션을 사용하면 warning 이 발생하지만 -isystem
옵션을 이용하면 발생하지 않는 것을
볼 수 있습니다.
$ cd linux-source-x.xx.x
$ cpp -dD -I include -I include/uapi -I arch/x86/include -I arch/x86/include/uapi \
-I arch/x86/include/generated -I arch/x86/include/generated/uapi \
<<< $'#include <linux/kconfig.h>\n#include <linux/jiffies.h>' > /dev/null
. . .
arch/x86/include/asm/processor.h:188: warning: "cache_line_size" redefined
188 | #define cache_line_size() (boot_cpu_data.x86_cache_alignment)
|
. . .
--------------------------------------------------------------------------------
# -isystem 옵션을 이용해 시스템 디렉토리로 설정하면 warning 이 발생하지 않는다.
$ cpp -dD -isystem include -isystem include/uapi \
-isystem arch/x86/include -isystem arch/x86/include/uapi \
-isystem arch/x86/include/generated -isystem arch/x86/include/generated/uapi \
<<< $'#include <linux/kconfig.h>\n#include <linux/jiffies.h>' > /dev/null
$
시스템 헤더로 설정하는 방법에는 #pragma GCC system_header
를 이용하는 방법도 있습니다.
이 방법은 디렉토리와 상관없이 적용이 되고 매크로가 파일 중간에 사용되면 이후
내용에 대해서만 적용됩니다. ( 이것은 헤더 파일에서만 사용할 수 있습니다.)
-nostdinc
-nostdinc++
이 옵션을 사용하면 헤더 파일을 찾을 때
gcc 에 기본적으로 설정되어 있는 standard system directories 를 찾지 않습니다.
-I
, -iquote
, -isystem
, -idirafter
옵션으로 설정한 디렉토리만 찾게 됩니다.
-Wsystem-headers
시스템 헤더 에서도 warnings 을 출력하게 합니다.
Quiz
리눅스 커널 소스에서 linux/pci.h
헤더를 include 했을때 사용할 수 있는 함수를 보려면 어떻게 할까요?
$ cd linux-source-x.xx.x
$ cp /boot/config-`uname -r` ./.config
$ make init/main.i # 처음 한번만 실행
$ header-function <<\@ -- -D__KERNEL__ \
-I./arch/x86/include -I./arch/x86/include/generated -I./include \
-I./arch/x86/include/uapi -I./arch/x86/include/generated/uapi \
-I./include/uapi -I./include/generated/uapi | less
#include <linux/kconfig.h>
#include <linux/pci.h>
@
. . .
---------------------------------------------------------------
# Diagnostics 메뉴에서 작성한 header-* 함수에 다음을 추가.
_header-kernel () {
local v dash="--"
for v do [ "$v" = "--" ] && { dash=""; break ;}; done
${FUNCNAME[1]%?} "$@" $dash \
-D __KERNEL__ -D CC_USING_FENTRY \
-include ./include/linux/kconfig.h \
-I./arch/x86/include -I./arch/x86/include/generated -I./include \
-I./arch/x86/include/uapi -I./arch/x86/include/generated/uapi \
-I./include/uapi -I./include/generated/uapi
}
header-functionk () { _header-kernel "$@" ;}
header-macrok () { _header-kernel "$@" | sed -E '/^#define CONFIG_|^#include <generated\/autoconf\.h>/d' ;}
header-typedefk () { _header-kernel "$@" ;}
header-structk () { _header-kernel "$@" ;}
header-enumk () { _header-kernel "$@" ;}
header-dumpk () { _header-kernel "$@" | sed -E '/^#define CONFIG_/d' ;}
$ header-functionk linux/pci.h
. . .
$ header-structk net/ip.h -s 'sk_buff\b'
. . .
$ header-functionk ext4.h -- -I fs/ext4
. . .
$ header-functionk '#include "fs/ext4/ext4.h"'
. . .
$ header-functionk linux/syscalls.h -- -D CC_USING_FENTRY
. . .
$ header-functionk <<\@ -s IS_ERR
#if defined(CONFIG_FUNCTION_TRACER)
#define CC_USING_FENTRY
#endif
#include "init/main.c"
@
. . .
파일 단위로 전처리 완료된 결과를 보려면 다음과 같이 하면 됩니다.
$ cd linux-source-x.xx.x
$ cp /boot/config-`uname -r` ./.config
# net/ipv4/tcp.c 파일의 전처리 결과를 보려면
$ make net/ipv4/tcp.i
. . .
CPP net/ipv4/tcp.i <---- 전처리 완료된 파일
# arch/x86/kernel/vmlinux.lds.S 파일의 전처리 결과를 보려면
$ make arch/x86/kernel/vmlinux.lds
. . .
LDS arch/x86/kernel/vmlinux.lds <---- 전처리 완료된 파일
다음은 *.c
파일의 전처리 결과를 볼 수 있는 스크립트입니다.
make foo/bar.i
한것과 동일한 결과인데 헤더부분을 제외하고 foo/bar.c
파일 내용만
출력합니다. 결과를 clang-format 해서 출력하기 때문에 확장 결과에 따라 시간이
오래 걸릴 수 있습니다. ( 분 단위로 걸릴 수도 있음 )
$ cd linux-source-x.xx.x # 사용 방법
$ header-make block/ioctl.c # ioctl.c 파일만 출력됩니다. 헤더에 포함된 inline 함수
. . . # 내용을 보고 싶으면 block/ioctl.i 파일을 보면됩니다.
# "-d" (dump) 옵션은 clang-format 하지 않고 출력하기 때문에 빠르게 결과를 볼 수 있습니다.
$ header-make -d block/ioctl.c
function header-make ()
(
set -o errexit
dump=false
if [ $# -eq 0 ]; then
echo "Usage: $FUNCNAME filename.c" >&2
exit 1
elif [ $# -eq 1 ]; then
target_c=$1
else
[ "$1" = "-d" ] && target_c=$2 || target_c=$1
dump=true
fi
if ! target_p=`readlink -e "$target_c"`; then
echo "Error: $target_c file does not exist" >&2
exit 1
fi
tmpdir=`mktemp -d -p /dev/shm`
cp -f "$target_p" "$tmpdir"
target_o=$tmpdir/${target_c##*/}
trap '[ -e "$target_o" ] && mv -f "$target_o" "$target_p"; rm -rf "$tmpdir"' EXIT
sed -E -i 's/^\s*#\s*include .+/\n\a\n&\n\a\n/' "$target_c"
target_i=${target_c%.*}.i
make "$target_i" >&2
if [[ ! -e $target_i ]]; then
echo ">>> Failed: make $target_i" >&2
exit 1
fi
sed '/\a/,/\a/d' "$target_i" |
sed -zE 's/^.*\n# [0-9]+ "<command-line>" [0-9]+\n//' |
sed -E '/^(#define |#pragma |# [0-9]+ )/d' > "$tmpdir/expanded"
if $dump; then
sed -zE 's/^\n+/\n/' "$tmpdir/expanded" | vi --not-a-term -c 'set ft=c' -
exit
fi
awk -v dir="$tmpdir" 'BEGIN { num = 0; file = dir "/0000" }
/^}$/ { print > file; close(file); num += 1;
file = dir sprintf("/%04d", num); next }
{ print > file }' "$tmpdir/expanded"
printf "%s\n" "$tmpdir"/[0-9][0-9][0-9][0-9] |
xargs -P `nproc` -i sh -c 'clang-format -i -style="{IndentWidth: 4}" "{}"'
mv -f "$target_o" "$target_p"
cat "$tmpdir"/[0-9][0-9][0-9][0-9] | vi --not-a-term -c 'set ft=c' -
)