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;

참조: https://stackoverflow.com/a/74748724/1330706