프로그래밍언어

C언어의 문법

KyooDong 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 예제