-
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 예제
'프로그래밍언어' 카테고리의 다른 글
프로그래밍 언어 Names, Binding, Scopes (0) 2020.05.01 Lexical And Syntax Analysis (0) 2020.04.30 프로그래밍 언어 Semantic 표현 (0) 2020.04.29 프로그래밍 언어 Syntax 표현(기술) (0) 2020.04.27 CFG (Context Free Grammar) (0) 2020.04.13