변수 (Variables)
우리가 지금 고민하고 있는 언어는 명령형 언어이다.
명령형 언어는 '프로세서'와 '메모리'로 구성된 폰 노이만 구조의 컴퓨터에서 실행된다.
그리고 프로세스가 한 일의 결과는 메모리에 저장된다.
명령형 언어에서 메모리를 대표하는 것은 변수다.
즉, 변수는 메모리 cell 에 대한 추상화이다.
변수 설계시 고려 요소와 특성
변수를 설계할 때 고려하는 여러가지 요소가 있다.
- 범위 (scope) : 프로그램 내에서 변수가 유효한 범위 (공간)
- 수명 (lifetime) : 프로그램 내에서 변수가 유효한 시간, 범위와 수명은 항상 겹치지는 않는다.
- 타입 (type) :
변수 값의 범위와 사용 가능한 연산자의 집합을 결정한다.
변수를 설계할 때는 타입을 확인하는 방법도 필요하다. (type checking), 또한
type compatibility, 즉, 정수형 변수와 실수형 변수를 곱할 수 있는지, 언어에서 허용하는지, 특정 연산을 지원하는지 (제곱 연산은 파이썬은 지원하지만 C++은 지원하지 않음) 고려해서 설계해야 한다.
- 값 (value) :
변수가 할당된 위치에 있는 내용.
변수의 l-value 는 그 변수의 주소이고, r-value는 그 변수의 값이다.
또, 하나의 셀 말고도 여러개의 셀이 모여있는 (배열, 구조체) 경우도 고려해서 메모리 공간 하나하나의 역할도 정해져 있어야 한다.
변수를 설계할 때는 변수의 값을 초기화 (initialization) 하는 방법도 설계해야 한다.
- 주소 (address) : 변수에는 주소가 할당되어 있다. 하지만 변수의 주소는 고정될 수도 있지만, 프로그램의 어디에 있느냐에 따라 순서가 변할 수도 있다. 또 실행중에 서로 다른 시간에는 서로 다른 주소값을 갖고 있을 수도 있다.
만약 서로 다른 변수가 같은 메모리 cell을 가리키면, 이를 aliases 라고 부른다.
alias는 C, C++의 경우에는 포인터, 레퍼런스를 이용해서 만들 수 있다.
하지만 readability를 저해할 수 있다. 프로그래머가 하나의 메모리 셀에 대한 여러 이름을 기억해야 하기 때문이다.
변수 이름:
모든 변수가 이름을 갖고 있지는 않다. 프로그래밍 언어 내부에서 쓰이는 변수는 이름이 없을 수 있다.
하지만 이름이 없는 변수는 프로그래머가 건드는 변수가 아니다.
- 대소문자를 구분할 것인지 결정,
대소문자를 구분하면 나중에 헷갈릴 수도 있다. 즉, readability가 떨어진다.
C언어 기반의 언어들은 case sensitive 하다. 다른 언어에서는 아닌 경우도 많다.
C++, java, C# 에서는 클래스 이름 같은 것들을 미리 갖다 쓰고 있는 경우가 있어서, 같은 이름을 쓰면 충돌이 날 수 있다.
- 예약어 결정
readability를 위해 사용한다. while 과 같은 키워드를 변수로 사용하려고 하면 언어에서는 정해진 구문을 사용하지 않았다는 이유로 문법 에러를 낼 것이다.
또 main 과 같은 이름은 비록 키워드는 아니지만, 시작 함수의 이름으로 정해진 예약어이다.
따라서 이 이름은 마음대로 가져다 쓸 수 없다.
만약 이런 예약어가 너무 많다면 변수명을 뭔가를 쓰려고 해도 예약어와 너무 많이 겹칠 수 있어서 잠재적인 위험이 있으니 적당한게 좋다..
- 길이 제한 결정
몇글자까지 변수 이름으로 볼 것인지, 너무 짧으면 복잡한 프로그래밍을 할 수 없고, 길어도 문제다.
(C언어에서는 변수명 길이 제한은 자유롭게 짓되, 언어에서 구분해주는 것은 첫 63개 글자, external name은 31글자 까지만 구분해준다고 하기도 한다. C#과 자바는 제한이 없고, C++은 제한이 없지만, 컴파일러에 따라 제한이 있기도 하다.)
- 특수문자 활용 여부
PHP는 모든 변수가 $ 로 시작한다. Perl 에서는 변수이름 앞에 시작하는 문자로 타입을 구분한다.
루비는 @ 기호가 오면 instance variables 이고, @@로 시작하면 class variables 이다.
Binding
그러면 변수를 만들 때 하나의 변수(entity)는 앞서 정리한 6가지 attribute 정보를 갖고 있어야 한다.
(6가지 attribute 정보들이 변수와 연결되어야 한다.)
이 정보들(attributes)과 변수(entity)를 연결하는 것을 binding 이라고 한다.
Binding Time
그러면 바인딩은 언제 할까?
바인딩하는 시점을 binding time 이라고 하는데, 이 시점에 따라서 프로그래밍 언어의 특징이 변한다.
1. language design time
프로그래밍 언어를 디자인하는 시점에 바인딩한다.
while 이라는 키워드와 문법 구조를 묶는 것, * 라는 기호를 곱셈 연산자 또는 포인터로 쓰겠다고 바인딩 하는 것과 같은 것들이 해당한다.
2. language implementation time
부동소수점으로 실수를 디자인했는데, 실제로 몇 비트로 할 지는 언어 구현 시점에 결졍할 수도 있다.
원래는 64비트 표준으로 만들 수도 있지만, 내 컴퓨터 성능이 안좋아서 그냥 16bit로 하겠다고 할 수도 있다.
3. compile time
컴파일하는 시점에 변수와 타입을 묶는 것이다. (특히 C계열은 이때 정해진 타입이 바뀌지 않는다.)
4. Load time
프로그램의 기계 코드를 메모리에 로드할 때, 메모리의 위치가 정해진다.
특히 C, C++ 에서 static 타입으로 정의한 변수들은 메모리 위치가 확실하게 정해진다.
5. Runtime
실행중에는 static이 아닌 변수들의 메모리를 할당해주고, 변수의 값을 바인딩하는 것도 계속 바뀐다.
바인딩 종류
바인딩은 크게 static binding 과 dynamic binding 으로 나뉜다.
정적 바인딩은 런타임 이전에 바인딩이 발생하고, 이후에 프로그램이 실행되는 동안 바뀌지 않는 것을 말한다.
동적 바인딩은 실행 중에 처음 바인딩이 발생하고, 프로그램이 실행되는 동안 바뀔 수 있는 것을 말한다.
그렇다면 변수와 type 은 어떻게 바인딩할 수 있을까?
type은 어떻게 파악하고, 바인딩 될까?
만약 static binding을 하다면, 타입은 명시적으로 특정될 수도, 컴파일러에 의해 암묵적으로 특정될 수도 있다.
명시적으로 정해주는 경우는 explicit declaration 라고 한다.
변수의 타입을 명시적으로 프로그램 statement를 이용해서 선언하는 것이다.
암묵적으로 정해주는 경우를 implicit declaration 이라고 한다.
이때는 타입을 특정하기 위한 default 메커니즘이 정해져있다.
Basic, Perl, Ruby, JavaScript, PHP 와 같은 언어들은 implicit delaration 을 지원한다.
암묵적 선언을 지원하면 writeability 가 증가하는 장점이 있지만, reliabilty가 감소하는 단점이 있다.
예를 들어 ya 라는 변수를 만들어서 쓰고 있는데, 오타를 내서 yb로 변수를 사용하는 것이다.
그런데 이때 암묵적 선언을 지원하면 에러를 내지 않고 프로그램이 실행될 가능성이 있다.
또는 원치않는 값 (디폴트로 0을 넣어준다거나) 이 들어가서 프로그램 실행이 의도치 않게 바뀔 수 있다.
일부 언어들은 프로그램이 시작되었을 때, 타입을 모르겠으니 알아서 정하고 컴파일하도록 동작하는 경우도 있다.
C#, Visual Basic, ML, Haskell, F#과 같은 언어들은 문맥을 보고서 타입을 마음대로 정해주는, type inferencing 메커니즘을 사용한다.
이는 타입을 dynamic binding 한다는 이야기와 같다.
dynamic binding
자바스크립트, 파이썬, 루비, PHP, C#(제한적) 과 같은 언어들은 동적 타입 바인딩을 지원한다.
이런 경우 보통 대입 연산이 수행될 때 타입이 특정된다.
이때는 flexibility가 증가하는 장점이 있지만, 실행중에 계속 타입을 체크하기 때문에 동적 타입 체크와 interpretation 에는 많은 비용이 들고 (실행시간 증가)
컴파일러를 통한 타입 에러를 찾는 것이 어렵다는 단점이 있다.
(복잡한 형태의 데이터를 보면 컴파일러가 타입을 특정하지 못해서 에러가 날 수도 있다.)
따라서 implicit type binding은 많은 문제를 내포하기 때문에 explicit type binding이 좋다.
Storage Bindings & Lifetime
메모리 저장공간과 변수를 바인딩하는 것을 storage bindings 라고 한다.
이때 이용가능한 메모리 셀 pool 에서 셀을 가져와 변수와 바인딩하는 것을 Allocation (할당) 이라고 한다.
거꾸로 cell 을 pool에 돌려주는 것을 Deallocation (반환) 이라고 한다.
할당과 반환은 모두 실행시간 (runtime) 중에 발생한다.
따라서 memory allocation은 dynamic memory allocation 을 일컫는다. (static 할 수도 있긴 하다.)
변수의 lifetime은 메모리의 할당과 반환 관점에서 보면, 할당된 시간부터 반환될 때까지의 시간을 변수의 lifetime 이라고 한다. 따라서 lifetime 은 메모리의 공간을 할당하고 반환하는 것과 관련된 이야기다.
변수의 lifetime은 아래와 같이 4가지로 분류할 수 있다.
- Static
프로그램이 실행하기 시작할 때 변수가 생긴 경우다.
변수가 생겼다는 것은 변수에 연관된 attribute 들이 다 확보되었다는 이야기다.
이 attribute 중에 메모리까지 변수에 binding이 되는 경우다.
static은 바인딩된 메모리가 프로그램이 끝날 때까지 유지된다.
예를 들어 C, C++의 static 키워드를 사용한 변수들은 프로그램이 시작되면 미리 해당 변수들의 메모리 공간을 다 잡아두고 프로그램이 끝날 때까지 해당 변수와 메모리 셀 사이 바인딩을 유지한다.
장점 : static변수는 프로그램의 실행 중간 결과를 저장하고 유지할 수 있으면서, 메모리를 어디에 잡아야 할 지 미리미리 다 알고 시작할 수 있다. (효율성이 높다.)
단점: 새로운 값을 사용하면 이전 값은 없어진다.
따라서 static 변수는 재귀함수에 사용할 수 없다. 계속 값이 덮어씌워지기 때문이다.
따라서 유연성이 낮다. - Stack Dynamic
static 변수는 재귀에 사용할 수 없기 때문에 재귀에 사용할 수 있는 stack-dynamic 부류의 lifetime도 존재한다.
storage binding이 동적이다. 즉, 변수가 선언된 코드가 실제로 실행될 때 storage binding이 생성된다.
이 변수는 스택 메모리를 사용하는데, 프로그래밍 언어의 런타임 시스템이 스택을 제공한다. 프로그램마다 스택이 하나씩 생긴다.
이렇게 생긴 스택은 함수가 return 될 때 없어진다.
따라서 이 변수의 lifetime은 변수가 선언된 함수/블록의 영역이 된다.
scalar 변수의 경우, 주소를 제외한 모든 attribute 가 정적으로 바인딩되어있다.
장점: stack-dynamic 분류의 바인딩된 변수는 재귀를 할 수 있다.
예를 들어, C에서는 함수안에서 static과 같은 키워드 없이 선언한 변수는 그 함수 내에서만 사용할 수 있다.
함수의 실행이 종료되면 사라진다.
즉, 재귀가 될 뿐만 아니라, 안쓰는 변수는 그때 그때 없애버리니, 메모리 공간을 효과적으로 사용할 수 있다.
단점: 변수를 그떄 그때 할당하고 없애는 과정은 런타임 시스템이 해준다. 그래서 이 과정을 위해 오버헤드가 발생한다. 그리고 서브프로그램은 그 변수의 값을 유지할 수없다. 서브프로그램이 종료되면 변수의 값은 버려지므로, 아무도 서브프로그램이 한 일을 모른다.
마지막으로 stack에 push 할 때마다 주소값이 변하므로, 이를 관리해주는 것도 필요하다 - Explicit heap-dynamic
stack dynamic의 경우 메모리의 할당과 해제를 런타임 시스템이 해야했다.
또 서브 프로그램(함수)의 실행이 종료되면 그 내부 데이터가 모두 사라지는 문제가 있었기 때문에 프로그래머가 직접 메모리를 할당하고 데이터를 저장하고, 사용이 끝나면 반환할 수 있는 기능이 필요했다.
Explicit heap-dynamic은 프로그래머가 명시적으로 메모리를 할당하고, 반환하는 코드를 작성하여 런타임에 메모리가 할당되고 반환되도록 한다.
이런 변수는 pointer, reference를 통해 접근할 수 있다. C++의 new, delete 키워드를 사용하거나, 자바의 모든 오브젝트는 참조를 통해 접근한다.
장점 : 동적으로 저장공간을 관리할 수 있는 방법을 제공한다.
단점 : OS와 소통하는 비용이 있기 때문에 효율적이지 않다. 프로그래머가 실수할 수 있는 여지가 있기 때문에 신뢰성이 낮다. - Implicit heap-dynamic
프로그래머가 변수를 선언했을 때 메모리를 할당하는 게 아니라, assignment statement가 나오면 그때 변수를 할당하고 반환하는 과정이 일어난다. 암시적으로 메모리를 할당하고 반환하는 과정이 일어나는 것이다.
[예시]
APL의 모든 변수는 암시적으로 할당된다.
Javascript, PHP, Perl 의 문자열과 배열은 암시적으로 할당된다.
장점 : 유연성이 증가한다.
단점 : 런타임 시스템이 할 일이 증가하므로 비효율적이 된다. 또 에러가 발생했을 때 프로그래머가 할 수 있는게 없다.
(에러 감지가 힘들다.)
Scope
lifetime이 변수를 사용할 수 있는 시간적 범위를 말한다면, scope 는 변수가 사용될 수 있는 '공간적' 범위를 말한다.
어떤 함수(program unit) 안에서 선언한 변수는 그 함수(unit) 밖에서는 보이지 않는다.
이런 변수를 local variable 이라고 한다.
그런데 어떤 변수는 함수 안에서 선언하지 않았음에도 가져다가 사용할 수 있는 변수들도 있다.
이런 변수는 nonlocal variables 라고 한다.
이 변수들은 함수 내에서 선언되지 않았으므로 nonlocal variables 로 분류한다.
Global variables 는 nonlocal variables 의 특별한 종류이다.
nonlocal variables는 내가 선언하지도 않았는데 사용할 수 있기 때문에 free variables 라고도 부른다.
(내가 선언하지 않았으니 내 메모리를 사용하지 않고 공짜로 사용한다는 의미이다.)
만약 함수 안에서 nonlocal variable을 사용하려고 할 때, 내가 정말 가져다 쓰고 싶은 변수가 어떤 것인지 결정해야 한다. (함수 외부에 같은 이름의 변수가 여러개 있을 수 있으므로) 이때 어떤 변수를 가져다가 사용할 지는 프로그래밍 언어를 디자인하는 사람들이 규칙을 만들어야 하며, 이 규칙을 scope rule 이라고 한다.
scope는 크게 static scope, global scope, dynamic scope로 분류된다.
Static Scope
static 이라는 말은 실행시간 중에 변하지 않음을 의미한다.
즉, 실행중에 변하지 않는 scope를 의미한다.
이런 스코프는 프로그램 소스코드(program text)를 통해 파악할 수 있다.
위 그림에서 k 라는 변수가 어디에 선언된 변수인지는 코드를 작성하는 프로그래머도, 이를 컴파일하는 컴파일러도 모두 알아야 한다.
이때 컴파일러가 k 라는 변수가 어디서 선언되었는지 찾는 과정을 search process 라고 한다.
간단한게 현재 변수를 사용한 위치의 scope부터 시작해서 한칸 한칸 바깥 scope를 탐색하면서 변수를 찾는다.
이때 어떤 static scope를 감싸고 있는 바깥 스코프를를 static ancestors 라고 한다.
그리고 static ancestors 중에서 현재 static scope와 제일 가까운 스코프를 static parent 라고 한다.
위 그림에서는 g 가 f 의 static parent 이고, g, h가 static ancestors 이다.
일부 언어는 중첩된 서브프로그램 정의를 허용한다. (함수 h 안에 함수 g를 정의하고, g 안에 f 를 정의하고..)
이 경우, nested static scopes 가 생성된다.
대표적인 언어들이 Ada, JavaScript, Common Lisp, Scheme, Fortran 2003+, F#, Python과 같은 언어들이 있다.
이때 변수들은 같은 이름을 가진 더 가까운 범위의 변수에 의해 가려질 수 있다.
while () {
int count = 0; // a
{
int count = 10; // b
count++;
}
}
위 코드에서, count++; 코드는 b 로 표시한 카운트 변수를 증가시킨다.
이때 이 블록 내부에서는 a 로 표시한 카운트 변수에 접근할 수 없다.
이런 경우를 스코프에 구멍이 있어서 안보인다고 표현한다. (hole in scope)
이런 방식의 코딩은 C, C++ 에서는 할 수 있지만, C#, Java 에서는 할 수 없다. (에러 발생)
함수형 언어의 변수 선언 (let construct)
대부분의 함수형 언어는 명령형 언어에서 하는 것과 비슷하게 let 이라는 키워드가 존재한다.
이를 let construct 라고 하는데, let construct 는 2가지로 구분한다.
1. 변수명과 값을 바인딩한다.
2. 사전에 바인딩하여 정의된 이름을 사용하여 새로 바인딩한다.
F#에서는 let left_side = expression 형식으로 사용하는데, 이때 left_side에는 이름 뿐만 아니라 tuple 패턴도 올 수 있다.
변수의 선언 순서
C99, C++, Java, C#은 변수를 어디에서든 사용할 수 있도록 허용한다.
C#을 제외하고는 모든 local variable의 스코프는 선언된 위치부터 그 변수가 위치하는 블록이 끝날 때까지다.
(C99는 1999년에 개정된 C를 말한다. C언어는 개정되더라도 이전 버전과의 호환성을 위해 조심히 개정해야 한다. 즉 개정된 언어로도 개정 이전에 작성한 프로그램을 실행할 수 있어야 한다. 이를 backward compatibility 라고 한다.)
그런데 C#은 조금 다르다.
변수의 스코프가 선언된 블록의 시작부터 끝까지를 스코프로 인식한다.
(즉, 변수의 선언 위치에 상관없이 그 변수가 선언된 블록 전체가 변수의 스코프이다.)
그래서 코드를 실행할 때 문제를 일으키기도 한다.
C++, C#, Java는 for문 안에서도 변수를 선언할 수 있다.
이때 이 변수들의 scope는 for문 내부로 제한된다.
static scope 방법의 평가
static scope는 프로그래머도 컴파일러도 알아보기가 쉽다는 장점이 있었다.
그런데 만약 소스코드가 매우 길이진다면 프로그래머도 컴파일러도 스코프를 파악하는데 시간이 오래 걸릴 것이다.
거기에 만약 f가 g를 부르고, g가 h를 부르는데, h가 다시 f를 부르는 식으로 서로 순환되는 관계가 생기면 변수의 스코프를 파악하는 것이 힘들 것이다.
그래서 static scope는 지역변수가 글로벌이 되기도 하면서 초기에 설계한 구조가 파괴될 수 있다.
Global Scope
ANSI C 문법을 보면 C, C++은 함수 선언과 global variable 선언은 동일한 레벨로 취급했다.
C, C++, PHP, Python 은 모두 global variable 을 지원한다.
즉, 함수 밖에서 변수를 선언하는 것을 지원한다.
게다가 C, C++은 다른 파일에서 선언된 변수도 가져다가 사용할 수 있다.
PHP는 HTML에 embedded 된 언어로서, 함수 내에 선언된 변수는 그 안에서 local variables 로서 scope를 가진다.
함수 밖에 선언된 변수는 global 변수로 취급하는데, scope hole 에 빠진 경우를 제외하고는 글로벌 변수를 사용할 수 있다.
이때 글로벌 변수를 사용할 때는 $GLOBAL 배열이나, global 키워드를 통해 접근할 수 있다.
파이썬도 함수 내에서 global variable 을 사용할 때는 global 키워드를 사용하여 해당 변수가 선언된 경우에만 사용할 수 있다.
Dynamic Scope
동적 스코프는 그 영역을 런타임에 실행하면서 파악한다.
callee 에서 사용한 변수를 caller에게 가서 찾는 것이다.
big() 함수 안에서 sub1을 정의하고, sub1안에서 sub2 함수를 정의했다.
위 코드로 볼 때, static scoping 방식을 사용하면 sub2 에서 참조하는 x는 big의 x다.
sub1의 x는 그 내부에서만 사용하는 변수이므로, sub2입장에서 전역적으로 선언된 변수는 big의 x이다.
그러나 dynamic scoping 방식을 사용하면 sub2 에서 참조하는 x는 sub1의 x이다.
callee에게 없는 변수는 caller에게 가서 찾기 때문이다.
따라서 이때는 x에 7이라는 값이 들어있게 된다.
이를 통해 동적 스코핑과 정적 스코핑에 따라서 프로그램의 실행 결과가 달라지는 것을 볼 수 있다.
따라서 언어를 설계할 때, 어떤 방식을 사용할 지 분명하게 정해주어야 한다.
Dynamic Scoping 방식의 평가
장점 : 편리하다.
단점 : 서브 프로그램(함수)이 실행되는 동안 그 서브 프로그램의 변수들이 그 서브 프로그램이 호출하는 서브프로그램들에게 보이는 문제가 있다. 또 정적 타입체킹을 할 수 없어서 readability가 좋지 않다는 단점이 있다.
Scope and Lifetime
이 두 개념은 꽤 깊은 관련이 있지만, 엄연히 다른 개념이다.
C, C++ 에서 함수안에 선언된 static 변수를 생각하면 scope는 함수 내부로 제한되지만, 변수의 메모리는 프로그램의 실행 시작부터 종료까지 쭉 할당되어 있다.
Referencing Environments
참조 환경은 현재 statement 에서 보이는 모든 이름의 집합을 말한다.
만약 z 함수에서 h 함수에 선언된 변수를 참조하고 있다면, z 함수의 참조 환경은 h까지 포괄하게 된다.
static scoping 방식을 사용하는 언어의 경우, 지역 변수는 인접한 스코프의 모든 변수에 대한 참조 환경을 가진다.
서브 프로그램(함수)은 호출되어 실행되고 나서부터 끝나기 전까지 active 하다고 한다.
dynamic-socped 언어에서는 참조환경이 지역 변수에 더해 모든 active 한 서브프로그램의 참조 환경 내 변수를 포함한다.
Named Constants
named constants는 stoarge에 바운딩 될 때만 그 값이 바운딩 되고, 이후에는 값이 새로 바운딩되지 않는 변수를 말한다.
PI = 3.14; 라고 하면 PI는 named contant라고 한다.
장점 : readability, modifiability 가 좋다. (읽기 좋고 수정하기 편하니 유지보수하기 용이하다.)
따라서 파라미터에 상수값을 넘길 때, 3.14를 직접 넘기는게 아니라 PI로 넘길 수 있다.
(이를 프로그램을 parameteraize 한다고 한다.)
named constant에 값을 바인딩하는 것은 dynamic, static 모두 가능하다.
(static 방식은 manifest constants 라고 부른다.)
예시
C++, JAVA : 어떤 종류의 표현식도 대입할 수 있다. 즉, 런타임에 계산한 결과를 대입할 수 있으니 동적으로 바인딩 된다. (const A = 3.14 * 2)
C# 은 readonly, const 를 구분한다. const named constant는 컴파일 타임에 바운딩되고, readonly named constant는 동적으로 바인딩된다.
정리
- 변수 명을 디자인할 때는 다음을 고려해야 한다.
1. case sensitivity
2. special word (키워드, 예약어) 와의 관계
- 변수는 아래 6가지 특성을 갖는다.
name, address, value, type, lifetime, scope
- Binding은 위 6개 특성과 프로그램 entity 사이의 결합을 말한다.
- Scalar Variable 는 다음과 같은 종류로 분류할 수 있다.
static / stack dynamic / explicit heap dynamic / implicit heap dynamic
Strong typing 의 의미는 모든 타입 에러를 검사한다는 의미다.
즉, 변수를 사용하기 전에 반드시 타입을 선언해서 컴파일 타임에 타입을 체크하겠다는 것을 말한다.
'CS > 프로그래밍언어론' 카테고리의 다른 글
[프로그래밍언어론] 10. Data Type (0) | 2024.06.13 |
---|---|
[프로그래밍언어론] 9. 함수형 언어 (LISP) (0) | 2024.06.12 |
[ Lex/Yacc ] 내가 만든 테스트케이스 (테스트 파일 포함) (0) | 2024.05.08 |
[ lex / yacc ] error: 'AUTO' undeclared (first use in this function) 해결 (0) | 2024.05.03 |
[프로그래밍언어론] 7. Parsing Problem (0) | 2024.04.19 |