ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • C언어의 문법
    프로그래밍언어 2020. 4. 13. 08:57
    728x90

    규동 언어를 CFG로 표현하는 법을 해보았으니 이번에는 C언어를 표현해보죠

    CFG 나 BNF 에 대해 잘 기억이 안나 시는 분들은 위 링크에 잠깐! 다녀오세요 ㅎㅎ 10분이면 충분합니다~

     

    C언어의 if 문은 어떻게 생겼니? 라고 묻는다면 아마 대부분은 "음~ if, 괄호, 조건문, 괄호 닫고, 중괄호 열고, 코드들이 쭉 와"라고 말할 테지만 반은 맞고 반은 틀린 표현이에요.

    기술 면접에서도 물어볼 수 있는 이 질문에 대해 정확히 답하려면 오늘 배울 내용이 반드시 필요할거에요.

     

    C언어의 BNF(CFG) 표현법

    프로그램

    <program> => <translation_unit>

    <translation_unit> => <external_declaration> | <translation_unit> <external_declaration>

    <external_declaration> => <function_definition> | <declaration>

     

    프로그램은 어떻게 생겼니?

    프로그램은 translation_unit으로 만들어졌어~

    translation_unit이 뭔데?

    translation_unit은 external_declaration 또는 translation_unit external_declaration 으로 만들어졌어

    external_declaration 이 뭔데?

     

    이런 식으로 계~~~~속 꼬리를 무는 질문을 하다 보면 최종적으로는 변수/상수란 뭔데?

    변수 상수는 ~~ 한 거야! 까지 모두 설명하게 된다면 C언어의 문법을 다 설명하게 되는 거예요

     

    일반 선언문(Declaration)

    <declaration> => <declaration_specifiers> <init_declarator_list>;

    <declaration_specifiers> => <type_specifier> 

        | <storage_class_specifier>

        | <type_qualifier>

        | <type_specifier> <declaration_specifiers>

        | <storage_class_specifier> <declaration_specifiers>

        | <type_qualifier> <declaration_specifiers>

    <storage_class_specifier> => auto

        | static

        | typedef

        | register

        | extern

    <type_qualifier> => const | volatile

    <init_declarator_list> => <init_declarator> | <init_declarator_list>, <init_declarator>

    <init_declarator> => <declarator> | <declarator> = <initializer>

     

    <declaration> 은 static int a = 10; 이런 것들을 말해요

    <storage_class_specifier>가 <declaration_specifiers> 에 의해 재귀적으로 호출될 수 있죠?

    그렇다는건 typedef typedef typedef int a; 이런 구문도 가능하다는 거예요. 문법적으로는 요

    하지만 언어라는 건 문법만으로 정의되지 않아요. 의미(Semantic)도 문법 못지않게 중요하죠

    컴파일러는 문법적 오류도 잡아내지만 의미적 오류도 잡아내 줘요. Semantic 을 표현하는 방법은 예시를 드는 방법이 가장 좋아요

    지금까지 적어놓은 것들만으로는 static int a = 10; 을 derivate 할 수 없어요 더 적어볼게요

     

    타입 명시자 (Type_specifier)

    <type_specifier> => <struct_specifier> | <enum_specifier> | <type_identifier>

     

    Struct_specifier

    <struct_specifier> => <struct_or_union> IDENTIFIER { <struct_declaration_list> }

        | <struct_or_union> { <struct_declaration_list> }

        | <struct_or_union> IDENTIFIER

    <struct_or_union> => struct | union

    <struct_declaration_list> => <struct_declaration> | <struct_declaration_list> <struct_declaration>

    <struct_declaration> => <type_specifier> <struct_declarator_list>;

    <struct_declarator_list> => <struct_declarator> | <struct_declarator_list>, <struct_declarator>

    <struct_declarator> => <declarator> | <declarator ... > : <expression>

     

    <struct_specifier> 는 구조체 선언 혹은 구조체 변수 생성하는 경우를 나타내요

    IDENTIFIER 는 변수명 같이 사용자가 임의로 만들어낼 수 있는 label들을 말해요 int a; 에서 a 가 IDENTIFIER가 되는 거예요

     

    <struct_declarator> => <declarator> | <declarator ... > : <expression>

    <declarator> 는 int a 같은걸 말하는데 <declarator ... > : <expression> 가 좀 생소하죠?

    ... 은 option 을 의미해요. 즉 있어도 되고 없어도 된다라는 뜻이에요

    위 문장은 구조체의 비트 필드 변수를 말해요 int a : 10; 이런 구문 쓰면 4바이트 중에서 10바이트만 a에 할당되잖아요 그걸 말하는 거예요

     

    <declarator ... > 이니까 int a 없이 몇 비트인지만 명시하는 게 문법적으로는 가능하지만 이 또한 의미적으로 틀리기에 실제로 사용은 불가능해요

    이거 쓰다가 안건대 Union 도 비트 필드 사용이 가능하군요!

    Enum Specifier

    <enum_specifier> => enum IDENTIFIER { <enumerator_list> }

        | enum { <enumerator_list> }

        | enum IDENTIFIER    

    <enumerator_list> => <enumerator> | <enumerator_list> , <enumerator>

    <enumerator> => IDENTIFIER | IDENTIFIER = <constant_expression>

     

    <constant_expression> 은 나중에 나오겠지만 상수들을 말해요

    Type Identifier

    <type_identifier> => <integer_type_specifier>

        | <floating_point_type_specifier>

        | <void_type_specifier>

        | <typedef_name>

    <integer_type_specifier> : signed, unsigned, char unsigned, short, long, int, char 등의 조합

    <floating_point_type_specifier> : float, double, long double

    <void_type_specifier> : void

    <typedef_name> : typedef 로 이전에 정의된 명칭

     

    이 부분은 다소 명확하게 정의되지 않았네요. 그래도 C언어를 아시는 분들이라면 무슨 말을 하는지는 쉽게 알 수 있을 거예요

    Declarator

    <declarator> => <pointer> <direct_declarator>

        | <direct_declarator>

    <pointer> => * <type_qualifier> | * <type_qualifier> <pointer> | * | * <pointer>

    <direct_declarator> => IDENTIFIER

        | ( <declarator> )

        | <direct_declarator> [ <constant_expression ... > ]

        | <direct_declarator> ( <parameter_type_list ... > )

     

    중요한 선언문(declarator) 에 대한 내용인데요

    int *a;

    같은 경우에는 int = <type_specifier> *a 가 <declarator>에 해당합니다.

    <type_qualifier> 는 위에도 나오지만 const, volatile 같은 키워드를 말하고요

     

    ( <declarator>   =    int (*a);

    <direct_declarator> [ <constant_expression ... > ]  = 배열   =          int a[10];

    <direct_declarator> ( <parameter_type_list ... > =  함수 포인터 or 함수 선언부 =       float (*fun) (int, int);

    Parameter_type_list

    <parameter_type_list> => <parameter_list> | <parameter_list> , ...   // 가변인자

    <parameter_list>=> <parameter_declaration> | <parameter_list> , <parameter_declaration>

    <parameter_declaration> => <declaration_speicifers> <declarator> | <declaration_specifiers> <abstract_declarator ... >

    <abstract_declarator> => <pointer> | <pointer ... > <direct_abstract_declarator>

    <direct_abstract_declarator> => ( <abstract_declarator> )

        | [ <constant_expression_opt> ]

        | ( <parameter_type_list_opt> )

        | <direct_abstract_declarator> [ <constant_expression_opt> ]

        | <direct_abstract_declarator> ( <parameter_type_list_opt> )

     

    이 친구들은 함수 선언부나 함수 포인터 정의할 때 사용됩니다.

    void add(int, int, ... ); 이런거요

    Initializer

    <initializer> => <constant_expression> | { <initializer_list> }

    <initializer_list> => <initializer> | <initializer_list> , <initializer>

     

    int a = 10;

    int a[2][2] = { {1, 2}, {3, 4} };

    statement (명령문)

    <statement> => <labeled_statement>

        | <compound_statement>

        | <expression_statement>

        | <selection_statement>

        | <iteration_statement>

        | <jump_statement>

    <labeled_statement> => case <constant_expression> : <statement>

        | default : <statement>

        | IDENTIFIER : <statement>

    <compound_statement> => { <declaration_list> <statement_list> }

    <declaration_list> => /* empty */  |  <declaration_list> <declaration>

    <statement_list> => /* empty */  |  <statement_list> <statement>

    <expression_statement> => ; | <expression>;

    <selection_statement> => if ( <expression> <statement>

        | if ( <expression> ) <statement> else <statement>

        | switch ( <expression> <statement>

    <iteration_statement> => while ( <expression> <statement>

        | do <statement> while ( <expression> );

        | for ( <expression ... > ; <expression ... > <expression ... > statement

    <jump_statement> => return <expression ... >;

        | continue;

        | break;

        | goto IDENTIFIER;

     

    생각보다 명령문 statement는 간단해요

    근데 주의할 점은 if 나 while 문의 괄호 안이 조건문 따위가 아니라 expression 이라는 거예요

    그래서 while ( a = 10); 같은 문장이 문법적 오류가 없는 거랍니다.

     

    compound_statement 는 복합문으로, 여러 statement를 {} 로 묶어내기 위해 만들어진 거예요

    보시다시피 declaration_list, statement_list 가 empty 가 가능해서 if (a = 10){} 도 가능한 거예요

     

    가장 중요한 건 if 문의 ambiguity 에요. ambiguity 는 같은 언어를 여러 개의 규칙으로 만들어낼 수 있는 걸 말해요

     

    if ( a == 10) if ( a > c ) else max = c;

    이런 구문이 있다고 해봅시다. 위 규칙에 따르면

    if ( a == 10) if ( a > c ) else max = c;

    if ( a == 10) if ( a > c ) else max = c;

    이렇게 두 가지 경우로 묶일 수 있어요 어떻게 처리될지는 컴파일러마다 달라요. 둘 다 틀리지 않아요 왜냐면 정의 자체가 모호하기 때문이에요. 다만 대부분의 컴파일러는 전자의 경우를 채택하고 있다는 것만 알고 있으면 돼요

    Expression

    <primary_expression> => IDENTIFIER

        | INTEGER_CONSTANT              // 1, 2, 3

        | FLOAT_CONSTANT                  // 1.0

     

        | CHARACTER_CONSTANT        // 'a'

        | STRING_LITERAL                     // "ab"

        | ( <expression> )

    <postfix_expression> => <primary_expression>

        | <postfix_expression> [ <expression> ]

        | <postfix_expression> ( <arg_expression_list> )

        | <postfix_expression> . IDENTIFIER            // student.id

        | <postfix_expression> -> IDENTIFIER          // student_pointer -> id

        | <postfix_expression> ++

        | <postfix_expression> --

    <arg_expression_list> => <assignment_expression> | <arg_expression_list> , <assignment_expression>

     

    연산자 우선순위들이 여기서부터 나와요

    우리가 흔히 우선순위 표를 검색하면 나오는 것들이 실제로는 다음과 같이 처리되고 있어요.

    이미 눈치채셨겠지만 위에 나올수록 우선순위가 높아요 즉 derivation 이 먼저 되는 것들이 우선순위가 높다는 거죠

    1 + 2 * 3; 이런 문장이 있을 때

    <expression> => <expression> <expression> => <expression> 2 * 3 => 1 + 2 * 3

    이건 대략적으로 쓴 것이고 지금부터 자세히 알아볼게요

    Unary Expression

    <unary_expression> => <postfix_expression>

        | ++ <unary_expression> 

        | -- <unary_expression> 

        | & <cast_expression> 

        | * <cast_expression> 

        | ! <cast_expression> 

        | - <cast_expression> 

        | + <cast_expression> 

        | sizeof <unary_expression> 

        | sizeof( <type_name> )

    <cast_expression> => <unary_expression> | ( <type_name> ) <cast_expression>

    <type_name> => <declaration_specifiers> | <declaration_specifiers> <abstract_declarator>    // (int *[]) a 

    Arithmetic Expression

    <multiplicative_expression> => <cast_expression>

        | <multiplicative_expression> * <cast_expression>

        | <multiplicative_expression> / <cast_expression>

        | <multiplicative_expression> % <cast_expression>

    <additive_expression> => <multiplicative_expression>

        | <additive_expression> + <multiplicative_expression>

        | <additive_expression> - <multiplicative_expression>

    <shift_expression> => <additive_expression>

        | <shift_expression> << <additive_expression>

        | <shift_expression> >> <additive_expression>

    Relational Expression

    <relational_expression> => <shift_expression>

        | <relational_expression> < <shift_expression>

        | <relational_expression> > <shift_expression>

        | <relational_expression> <= <shift_expression>

        | <relational_expression> >= <shift_expression>

    <equality_expression> => <relational_expression>

        | <equality_expression> == <relational_expression>

        | <equality_expression> != <relational_expression>

    Logical Expression

    <logical_and_expression> => <equality_expression>

        | <logical_and_expression> && <equality_expression>

    <logical_or_expression> => <logical_and_expression>

        | <logical_or_expression> || <logical_and_expression>

    <conditional_expression> => <logical_or_expression>

        | <logical_or_expression> ? <expression> <conditional_expression>

    Expression and Assignment

    <assignment_expression> => <conditional_expression>

        | <unary_expression> = <assignment_expression>

    <comma_expression> => <assignment_expression>

        | <comma_expression> , <assignment_expression>

    <expression> => <comma_expression>

    <constant_expression> => <expression>         // 의미적으로는 분명 다르나 문법적 모양은 같음

     

     

    Expression 예제

    declaration 예제

    댓글

Designed by Tistory.