[C] C11

spring·2020년 11월 9일
0

C11에 새롭게 추가된 문법들을 소개한다. 아쉽지만 Windows에서는 C11을 제대로 지원하는 무료 컴파일러는 없다. ICC가 지원한다고는 하나 확인해 보진 못했고 유료이다.
매우 아쉽게도 c11을 제대로 사용하려면 Linux로 가야한다.

_Noreturn, noreturn

_Noreturn 또는 noreturn 키워드는 c11표준으로 등장했다.
이 키워드는 해당 함수가 컴파일러에게 반환값이 없음을 명시하여 최적화에 도움을 준다.
예를들어 아래 fatal함수같은 경우는 절대로 반환값이 나올 수 없다.

#include <stdlib.h>
#include <stdio.h>
#if defined(__STDC_VERSION__) && __STDC_VERSION__>=201112
#   include <stdnoreturn.h>
#   if defined(_WIN32) || defined(_WIN64)
#       define NORETURN __declspec(noreturn)
#   else
#       define NORETURN noreturn
#   endif
#else
#   define NORETURN
#endif

NORETURN void fatal(void)
{
    fprintf(stderr,"error message\n");
    exit(1);
}

int main(void)
{
    int i = 1;
    if(i%2)
        fatal();
    puts("This code is never executed.");
}

c11이 지원되는 컴파일러의 경우 stdnoreturn.h을 사용하고 Visual Studio의 경우는 _-declspec(noreturn) 을 사용하면 된다.

_Alignof, _Alignas, alignas, alignof

_Alignas 또는 alignas 키워드는 해당 변수(의 시작주소)가 지정한 상수배수의 위치에 정렬되도록 한다. 이는 구조체의 패딩과도 관련이 깊다. 구조체의 패딩을 결졍하는것이 아니라 해당 변수에게만 지정된 상수의 배수 주소에 선언하게 하는 키워드이다.

이 키위드를 이해하기 전에 구조체 패킹 키워드인 __attribute__((__packed__))#pragma pack(N) 부터 제대로 알고 가야 한다.

__attribute__((__packed__))__attribute__((packed, aligned(N))) 은 struct를 선언할 시 struct 키위드 뒤에다 사용하면 된다.

아래의 2가지 방법 모두 사용 가능하다.

struct Z{
    char a;
    int b;
    float c;
}__attribute__((packed, aligned(1)));
struct __attribute__((packed, aligned(1))) Z{
    char a;
    int b;
    float c;
};

또한 __attribute__((__packed__))__attribute__((packed, aligned(1)))은 동일하다.

이 키워드는 구조체의 정렬 옵션을 제한한다. 구조체 내부의 모든 변수를 지정된 정렬배수에 맞추어 메모리 구조를 할당한다.

#pragma pack(N) 역시 마찬가지이다. 이 또한 구조체 내부의 모든 변수를 지정된 정렬배수에 맞추어 메모리 구조를 할당한다.

이제 _Alignas 키워드를 살펴 보자. 이 키워드는 해당 변수를 지정된 정렬 배수에 맞추어 메모리 구조를 할당한다.
당연히 나머지 변수의 키워드는 __attribute__((packed, aligned(N)))에서 지정한 대로 동작한다.

하지만 #pragma pack()을 사용했을 경우에는 _Alignof는 무시된다.

그러니까 우선순위를 정리하자면

#pragma pack()__attribute__((packed, aligned(N)))를 같이 사용하면 pragma pack 무시됨.

#pragma pack()_Alignas 를 같이 사용하면 _Alignas 무시됨.

_Alignas__attribute__((packed, aligned(N)))는 같이 사용 가능. 여기서 _Alignas로 지정하지 않은 변수는 __attribute__((packed, aligned(N))) 에서 지정한 정렬 배수를 따라가지만 _Alignof 키워드에서는 가장 큰 _Alignas 정렬 배수를 출력한다.

소름돋는 결과인 3가지 키워드를 모두 사용하면 #pragma pack에 의해 _Alignas는 무시되고
__attribute__((packed, aligned(N)))에 의해 pragma pack은 무시되어서
__attribute__((packed, aligned(N)))만 남게 된다.

이미 선언된 구조체의 정렬 배수를 알고 싶다면 _Alignof를 사용한다.

#include<stdio.h>
#include<stdalign.h>    //alignas, alignof
#include<stddef.h>      //offsetof

struct Z{
    alignas(4) char a[10];
    alignas(8) int b;
    char c;
    float d;
}__attribute__((packed, aligned(1)));

int main(){
    printf("sizeof = %ld\n",sizeof(struct Z));
    printf("alignof = %ld\n",alignof(struct Z));

    printf("offsetof a = %ld\n",offsetof(struct Z,a));
    printf("offsetof b = %ld\n",offsetof(struct Z,b));
    printf("offsetof c = %ld\n",offsetof(struct Z,c));
    printf("offsetof d = %ld\n",offsetof(struct Z,d));
    return 0;
}

_Exit

이 함수는 자원을 정리하지 않고 그냥 프로세스를 종료한다. 즉, int atexit( void (*func)(void) ); 함수나 int at_quick_exit( void (*func)(void) );를 호출하지 않는다.

이게 왜 C11이냐? 함수 프로토타입이 바뀌었다. 리턴값이 없으므로 함수 원형이 아래와 같이 바뀌었다.

_Noreturn void _Exit( int exit_code );

_Pragma

_Pragma(string-literal) 은 이미 알고 있는 #pragma와 동일하다.
왜 있는지 모르겠다.

#include<stdio.h>
#include<stdalign.h>    //alignas, alignof
#include<stddef.h>      //offsetof

_Pragma("pack(1)")  //same as #pragma pack(1)
struct Z{
    char a[10];
    int b;
    char c;
    float d;
};

int main(){
    printf("sizeof = %ld\n",sizeof(struct Z));
    printf("alignof = %ld\n",alignof(struct Z));

    printf("offsetof a = %ld\n",offsetof(struct Z,a));
    printf("offsetof b = %ld\n",offsetof(struct Z,b));
    printf("offsetof c = %ld\n",offsetof(struct Z,c));
    printf("offsetof d = %ld\n",offsetof(struct Z,d));
    return 0;
}

func

함수 이름을 지정하는 문자열 값의 매크로이다.... 기존에 있던 __FUNCTION__ 과의 차이는 모르겠다.

#include<stdio.h>
void foo(){
    puts(__func__);
}
void bar(){
    puts(__FUNCTION__);
}
int main(){
    printf("%s\n",__func__);
    foo();
    bar();
    return 0;
}

_Generic

매크로 관련 키워드이다. 아래와 같이 사용하며 이를 이용한 제네릭 프로그래밍이 가능하다.

#include<stdio.h>
void foof(float v){
    printf("float value v is : %f\n",v);
}
void fooi(int v){
    printf("int value v is : %d\n",v);
}
void food(double v){
    printf("double value v is : %f\n",v);
}
#define foo(V)  _Generic((V),float: foof,int: fooi,double: food,default: fooi)(V)

int main(){
    foo(5);
    foo(4.4F);
    foo(4.7);
    return 0;
}
profile
Researcher & Developer @ NAVER Corp | Designer @ HONGIK Univ.

0개의 댓글