Hi yoahn 개발블로그

#3 프로그래밍 언어 설계의 원칙, 구문 정의, 기초 의미론 본문

sswu/프로그래밍언어론

#3 프로그래밍 언어 설계의 원칙, 구문 정의, 기초 의미론

hi._.0seon 2020. 6. 29. 15:34
반응형

1. 프로그래밍 언어 설계의 원칙

- 바람직한 언어를 판단

- 좋은 프로그래밍 언어를 선택할 수 있다.

 

1) 효율 Efficiency

- 돈, 시간, 공간의 효율

 

▶ 최적화 용이성

- 실행 효율

- 초창기 언어일 수록 중요(컴퓨터가 귀한 자원)

- 프로그램이 짧은 시간&적은 메모리로 실행을 완료하려면 실행 파일이 효율적이어야 함

  -> 컴파일러가 실행파일을 효율적으로 만들도록 하는 것: 최적화

- 최적화가 얼마나 정교하게 높은 수준으로 이루어질 수 있는가

 

최적화가 쉽게 이루어지려면 언어가 어떻게 설계되어야 하는가

-> FORTRAN = 변수 길이 6글자 이하 (word=36bit, 글자인코딩=6bit=1글자)

                        -> 판독성은 떨어지고 인건비는 올라감

 

▶ 번역 효율

- 컴파일 시간이 빠른 것

1. go to 레이블이 go to 문장보다 먼저 나온 경우만 허용

  (레이블이 뒤에 있으면 go to 목적지를 바로 파악할 수 없어 나중에 레이블을 읽은뒤 다시 번역 -> 번역 2번)

2. 연산이 이루어질 때 변수 타입을 체크해서 이상하면 오류를 발생시키는 코드를 컴파일러가 추가 -> 컴파일 느림

-> 신뢰성은 올라감

- 타입체크하는 코드를 컴파일러가 만들지 않으면 -> 번역 효율은 상승, 신뢰성은 떨어짐 

 

▶ 구현 용이성

- 컴파일러 작성이 쉬운 것

- block { }: 효율적으로 컴파일하는 방법 -> Algol60 의 장애 요인

 

▶ 작성 용이성

- 프로그래밍(작성)의 효율

- 표현력 : 사람 머릿속의 추상화 매커니즘을 프로그래밍 언어가 가지고 있는 것

 

▶ 판독성

- 추상화로 가늠할 수 있다

- 판독성이 떨어지면 유지보수가 어렵다 -> 생산성 ↓

- 유지보수 단계에서 중요

 

▶ 신뢰성

- 버그 발생 확률이 적은 것

 

▶ 유지보수 용이성

- 판독성과 밀접한 관계

 

2) 규칙성  Regularity

언어 기능들의 통합 정도를 가늠하는 척도

- 특정 구문 사용에 별난 제약이 적은 것

- 구문 사이에 이상한 상호 작용이 적은 것

- 언어 동작에 예상치 않은 일이 적은 것

 

1. 일반성 generality

- 구문 사용에 특수 케이스를 피하고 밀접하게 관련된 구문을 일반적인 것으로 통합

- 파스칼: 기본적인 것들을 제약없이 다양하게 조립할 수 있는 조립 방법을 제공 =일반성

 

2. 직교성 orthogonality

- 구문이 어떠한 의미 있는 형태로도 결합 가능

- 프로그래밍 언어의 요소를 결합할 수 있도록 허용하는 것

직교성이 너무 높으면 -> 복잡, 비정상적인 상황이 발생할 가능성 높아짐

 

3. 일률성 uniformity

- 비슷한 형태는 비슷한 의미를 가진다. (다른 형태는 다른 의미)

-> 함수{  }, 클래스{  }; -> 의미가 비슷한데 다른 모양 -> 일률성이 깨짐

 

3) 단순성

- 도구는 단순해야 배우기 쉬움

- 너무 단순하면 작성용이성이 떨어짐, 표현력 부족

-> 최적의 선을 넘으면 안됨

4) 표현력

- 표현력이 좋으면 같은 내용을 더 간결하게 나타낼 수 있다

-> 판독성과 상충 가능

5) 확장성

- 사용자가 언어에 새로운 기능을 추가할 수 있는 매커니즘 존재

(연산자 오버로딩 -> 확장성, 표현력↑, 복잡성도 올라감)

 

2. 프로그래밍 언어의 구문 정의

언어 -> 구문, 의미

한국어 문장 

-> 1. 적법한 단어인지, 2. 문법 기준에 맞는지

 

구문 level

2.1 어휘 구조 Lexical Structure

- 프로그램을 구성하는 단어, 토큰의 구조

(토큰이 어떤 구조를 가지고 있느냐)

> 토큰 부류

   - 예약어  : if, while,  정해진 용도로만 사용 가능, 다른 용도 사용 불가

   - 리터럴 / 상수 : 글자 그대로가 자신의 값 / 상수

   - 특수 기호 : +, - , * ,  /

   - 식별자 : 변수명, 함수명, 클래스명, 상수 이름,,

(keyword : 사용자가 따로 선언해서 사용하는 것을 허용하는 경우)

 

< 토큰 기술 방법 >

1) 자연 언어

  - 자연언어로 설명

2) 정규 표현 regular expression

  - 일종의 언어

접합 , 선택 | , 반복 * +, ? .

(a|b)*c

2.2 구문 구조 Syntactic Structure

프로그램에서 토큰들이 조합되는 규칙 (=문법)

 

< 구문 구조 기술 방법 >

- 문맥-무관 문법 CFG

- 배커스-나우어  BNF

- 확장 배커스 나우어 형식  EBNF : BNF + 정규표현

- 구문 다이어그램

 

-> 모두 표현력이 동일하다

 

- 언어 구문의 정의는 의미와는 무관함

- 형식이 문법에 맞는가, 문법 형태만 체크

 

3. 기초 의미론

언어=문장들의 집합

-> 언어에 포함된 모든 문장들의 의미를 파악하면 그 언어를 파악한 것이다.

but, 불가능 -> 문장의 수 무한개

-> 문장들의 공통적인 요소들을 완전히 이해하고 나면 공통 요소들을 조립하여 큰 단위가 되는 의미 합성,,

-> 문장의 의미를 익혀나가면 모든 문장을 보지 않고도 어떤 문장이라도 이해할 수 있다.

 

프로그래밍 언어의 문장을 구성하는 요소

문장 sentence == 문법적으로 올바른 하나의 프로그램

C++ 언어 == c++로 작성 가능한 모든 프로그램의 총 집합

 

3.1 구문과 의미

프로그래밍 언어의 의미 기술은 구문 기술보다 훨씬 어렵다

- 구문 기술 표준 존재: CFG, BNF, EBNF, Syntax Diagram

- 의미 기술은 확고한 표준이 없다

 

● 프로그래밍 언어의 의미를 기술하는 일반적인 방법들

 1. 언어 참조 매뉴얼

-> 자연 언어로 기술

- 현재까지도 가장 많이 사용됨

 

 2. 정의적 번역기 (컴파일러/인터프리터)

- 컴파일러를 돌려보면서 의미를 유추

- 언어에 대한 명확한 정의를 바탕으로 컴파일러를 작성하는 것이 제대로 된 순서

 but, 거꾸로, 질문에 대해 컴파일러를 돌려보면서 의미를 유추하는 것 (본말이 전도)

- 컴파일러를 잘못 작성한 경우, 잘못된 의미가 퍼질 수 있다.

 

 3. 정형적 정의

- 이론적으로는 가장 이상적이지만 이해하기 힘들고 익혀 사용하는 것도 힘들다

- 다수가 동의하는 표준도 없음

 

3.2 속성과 바인딩

1) 언어

- 그 언어에 속하는 가능한 모든) 문장들의 집합

한국어=한국어 문장들의 집합

C++ = C++ 문장sentence(프로그램) 들의 집합

c++ sentence=c++ statement * N

 

- 언어의 의미 = 그 언어에 속한 '모든' 문장들의 의미

- 내가 모르는 것을 이해하려면 내가 이해하고 있는 것을 가지고 대응시키는 방법밖에 없음

 

2) 환원주의 Reductionism  (근대화~현대 주된 사상, 근대 서양 사상 ->환경오염)

: 전체를 쪼개서 부분을 이해하면 전체를 이해할 수 있다

 

<-> 전일주의 Holism (환경, 생태 쪽의 사상, 동양): 부분의 합이 전체는 아니다. 쪼개지 않고 그대로 이해해야 함

 

- 어떤 높은 단계의 개념을 더 낮은 단계의 요소들로 분할하여 정의하는 것

-> 복잡한 것을 단순한 것들의 조합으로 생각하는 것

 

$ 언어 의미에 대한 환원주의적 접근

- 임의의 한 문장의 의미

      = 문장을 구성하는 절, 구, 개별 단어 들의 의미, 결합될 때 추가되는 의미, 뉘앙스 -> 분석하여 파악 가능

- 임의의 한 C++ 프로그램의 의미

      = 프로그램을 구성하는 함수, 블록, 개별 식별자 등의 의미, 결합될 때 추가되는 의미, ,-> 분석하여 파악 가능

 

3) 프로그램을 구성하는 가장 기본적인 단위는 이름 | 식별자

- 변수, 상수, 연산자, 타입       ----> 공통요소 : 이름

int num;

const double PI = 3.14;

m%n;

typedef int score;

 

- 이름 += '종류'

   이름에 종류라는 성질이 추가

         -> 변수, 상수, 연산자, 타입 등으로 나눠짐

 

★ 가장 기본적인 단위의 의미 파악

- 기본 개체/구성체의 의미 -> 개체에 연계된 속성 값이 결정되면서 파악됨

개체의 연관된 속성을 파악하면서 그 개체에 대해 파악

 

속성: 연계된 개체의 의미를 결정하는 성질

- 세상 모든 객체들에 대한 모든 속성을 파악 -> 실제와 같은 가상 세계 : 매트릭스

4) 바인딩

- 개체/이름과 속성 값을 묶어주는 것이 바인딩

- 선언문 = 바인딩 이루어지는 매커니즘

 

★ 바인딩 타임

- 속성 값이 구해지면 그것이 개체/이름에 엮이는 시점

 

1. 정적 바인딩

- 실행시간 이전에 일어나는 바인딩

- 정적 바인딩으로 연결되는 속성 -> 정적 속성

 

> 언어 정의 시간: 언어 설계자가 의미를 정함 int, double, ,, 내장 타입

> 언어 구현 시간: int를 메모리 내에서 몇 바이트로 표현,,

> 번역 시간/컴파일 시간: 상수, 타입, 초기값

> 링크 시간: 라이브러리 함수를 연결, 함수 호출시 어떤 코드가 실행될건지

> 적재 시간: 많은 변수들의 실제 메모리 위치 (재배치 가능한 코드인지 결정)

           -> 메모리 위치를 미리 정할 수 없다. 총 필요한 공간은 미리 알 수 있음

               -> 적재/실행시간 이후에 실제 메모리 주소가 확정됨

 

2. 동적 바인딩

- 실행하는 동안 일어나는 바인딩, 머신 사이클 이후에 일어남

- 동적 바인딩으로 이름에 엮이는 속성 -> 동적 속성

> 실행 시간 / 수행 시간

 

※ 속성들의 바인딩 타임에 따라 언어의 의미에 영향

 

3-2. 기초 의미론

3.3 선언, 블록, 영역

- 바인딩은 묵시적, 명시적으로 이루어지는 것이 가능

묵시적 바인딩

- 오류를 잘 인식하지 못함 -> 잘못된 결과, 오류 찾기 어려움

- 주어진 정보의 부족으로 오류를 판단하는 근거가 부족함

명시적 바인딩

- 오류를 잘 잡음

- 선언으로 컴파일러에 정보를 더 많이 주는 것

 

*현대로 올수록 명시적으로 설계되는 경향

 

> 다수의 바인딩이 묵시적으로 설계된 언어도 존재

  -> 주로 변수 이름 붙이는 방식으로 결정 (변수 이름에 규칙 부여)

- FORTRAN: 변수 식별자가 I~N 으로 시작하면 정수타입, 그 외에는 실수 타입

- BASIC: 변수 식별자가 %로 끝나면 그 변수 타입은 정수, $로 끝나면 스트링, 그 외는 실수

 

정의 : 어떤 개체에 관련된 (잠재적인 모든) 속성을 바인딩하는 선언

선언 : 몇개의 부분적인 속성을 바인딩하는 선언

- 모든 속성 중 일부만 바인딩

- 나머지는 링크/적재/실행 이후에 바인딩

함수 원형 -> body가 생략, 데이터 타입만 기술 -> 선언

 

블록

- 프로그래밍 언어에서 선언으로 바인딩되는 범위를 제한

- 선언과 연계된 특정 구조를 지칭하는 이름

- Algol60에서 최초로 제안

연속된 선언,문장들의 연속을 { }, begin-end, do-end로 둘러싼 형태

 

지역적 선언: 특정 블록과 연관된 선언

비지역적 선언: 블록 바깥에 있는 선언

 

- 모든 Algol60 후손들은 블록 구조를 다양하게 제안 (거의 모든 현대 프로그래밍 언어)

- 블록 내에 다른 블록 내포 가능

    -> 내부 블록에 동일한 이름 재선언 가능

 

선언이 연계된 블럭 외의 구조들

구조체, 클래스, namespace, package, module

 

Scope 영역

- 프로그램에서 바인딩이 해체되지 않고 유지되고 있는 범위

- 선언을 통해 부여된 의미가 유지되고 있는 범위

-> 이름/프로그램의 의미를 알기 위해서는 영역을 분간해야 함

(영역 마다 의미를 구분)

 

# 정적 영역 바인딩

static scoping

- 프로그램 구조와 관련있다. (실행과는 관련 없음)

- 바인딩을 만드는 선언이  나타나는 블록과 내포된 블록에 영역이 한정됨

(프로그램 겉모습에 따라 영역 한정)

- 눈으로 이름의 의미를 파악 가능

 

# 동적 영역 바인딩

- 블록 구조 언어에서 바인딩을 만드는 선언이 나타난 블록과 그 블록을 호출한 블록에 영역이 한정됨

- 프로그램 실행과 관련됨

 

내부에 있는 선언이 우선 적용

- 동일한 이름의 선언이 내포된 블럭에 나타나면 바깥에 선언된 이름을 가림

-> 영역 구멍 = 기존 바인딩이 해체된 것은 아니므로 영역은 지속됨, 구멍에서 안보이는 것

 

가시성: 선언의 바인딩이 실제 적용되는 범위, 그 이름이 보일수 있는지없는지

3.4 이름 식별과 다중 적재

1) 이름 식별

- 이름=프로그래밍 언어를 구별하는 가장 기본적인 요소

- 개체에 연계된 속성 값이 그 개체의 의미를 결정

- 이름이 어느 영역에 포함된 것인지 판단해야 그 이름의 의미를 결정할 수 있음

   (-> 프로그램의 의미->프로그래밍 언어의 의미 아는 것)

- 이름 의미를 구분하지 못하면 프로그램 의미 알 수 없음

 

2) 다중적재 Overloading

중복정의, 과적

- 하나의 이름에 여러 의미가 실려있는 것

- 다중적재를 지원하지 않는 언어는 없다.

     (지원하지 않으면 이름 갯수가 많아짐)

 

다중적재 식별

- 이름의 여러 의미 중 하나를 고르는 것

다중적재-> 연산자, 함수

-> 다중적재된 이름의 의미를 파악하기 위해서 피연산자/매개변수타입, 개수 정보를 이용하여 의미 판단

(함수 호출 - 매개변수의 개수&타입 = 호출 문맥 (calling context))

 

연산자의 의미는 피연산자에 의해 구별되고, 

함수의 의미는 매개변수의 개수와 타입에 의해 구별된다.

 

- Ada

다중적재 식별에 호출 문맥 + 반환 타입도 이용

 

3.5 심볼 테이블

바인딩 정보가 저장되는 곳

 

- 선언에서 이루어진 바인딩이 저장되는 자료구조

  이름과 이름에 연관된 속성들을 가짐

- 컴파일과정에서 선언문을 보고 바인딩 정보를 추출해서 심볼 테이블에 저장

 

-> 컴파일과정에서 선언문을 보고 심볼 테이블에 바인딩 정보를 추출해서 저장

   -> 나중에 그 이름이 사용될 때 심볼테이블을 보고 바인딩 정보를 참조해서 코드를 번역

이름속성 / 바인딩
i종류=변수, 타입=int, 메모리=4byte, 영역,,

★ 컴파일러가 관리하는 핵심 자료

-> 엔트리의 검색/삽입/삭제가 빈번하게 일어남

** 효율적으로 작업할 수 있도록 그에 맞는 다양한 자료 구조를 이용해서 심볼 테이블을 구현

(이진검색트리, 해시 테이블)

 

- 블록-구조 언어를 위한 심볼 테이블 -> 논리적으로 스택처럼 작동함

(괄호와 같은 의미, 블록의 영역들이 괄호처럼 사용)

 

- 심볼 테이블에 저장될 바인딩을 만드는 선언의

  바인딩 타임에 따라 같은 프로그램이라도 다른 의미를 가짐(다른 결과)

1) 컴파일 타임에 선언 처리-> 정적 영역 바인딩

-> 컴파일 하면서 선언 처리

현대 프로그래밍 언어들에서 많이 사용

 

2) 실행 시간 때 선언 처리 -> 동적 영역 바인딩

-> 실행 때 심볼 테이블이 비어있지 않음, 전역 이름들이 처음에 들어있다.

함수, 전역변수, main,

- 이름에 대한 지역 선언이 없으면 바깥 블록의 선언을 찾는 것이 아니라

  현재 위치한 블록을 호출한 블록의 지역 선언을 찾는다.

 

- 동적 영역 바인딩의 경우, 

   -> 같은 문장이 다른 결과를 가져올 수 있음

(함수가 호출되는 지점에 따라 다른 결과)

문제가 많아 사용하는 언어가 거의 없음

Lisp이 초기에 동적 영역 바인딩을 사용했으나, 나중에 정적 영역 바인딩으로 바꿈

 

# 동적 영역 바인딩의 문제점

- 프로그램 구조만 보고 변수가 어느 선언이 사용될 지 알 수 없음

- 프로그램을 실행해봐야 알 수 있다.

 

실행 전에 비지역변수가 어느 것일지 예측 불가

  -> 데이터 타입도 동적으로 선언 = 동적 타입 바인딩

(동적 영역 바인딩은 동적 타입 바인딩을 수반함)

 

# 동적 영역 바인딩의 장점

- 매우 동적이고 인터프리터되는 언어는 실행 시간에 많은 속성들이 바인딩됨

 -> 심볼 테이블 관리를 실행타임으로 미뤄도 부담이 없다

(어차피 실행시간에 많은 속성 바인딩됨 -> 영역&타입이 동적으로 되어도 OK)

- 인터프리터에서는 동적 영역으로 구성된 환경을 구성하는 것이 쉽다. (정적 영역보다 간단, 사용자가 읽기 쉬운 것은 아님)

- 프로그램 환경에 따라 실행 결과가 결정됨

 

  • 연산과 함수는 의미에 있어 본질적인 차이가 없음, 구문의 차이만 존재
    • 연산자는 특수문자 사용, 함수는 식별자 사용
    • 연산 = 중위표기, 피연산자의 개수가 3개 이하
    • 함수 = 전위표기, 매개변수 개수가 무제한
  • 극단적인 다중적재
    • 일부 언어에서 (변수, 타입, 함수)종류별로 다른 심볼 테이블을 사용 -> 동일한 이름을 여러 종류로 사용하는 것이 허용됨
    • Java => Class A, int A, void A(A A)
      • 키워드를 예약하지 않는 경우 사용자가 이름으로 사용할 수 있음
      • 프로그램이 알아보기 어려워진다.

3.6 할당, 수명, 환경

1) 환경

- 이름에 바인딩된 메모리 위치들의 집합, (특정 주소 X, 추상화된 메모리 칸)

 

- 환경 구성 방식

   적재 시간에 정적으로 구성

   실행 시간에 동적으로 구성

   적재/실행 시간에 정적/동적으로 구성(혼합) -> 바인딩 시간의 차이

 

-> 각 방식에 따라 프로그래밍 언어의 능력 차이가 발생 = 언어의 의미 차이

- 이름들이 메모리 위치에 바인딩 됨

* 상수/데이터 타입의 이름은 컴파일 시간에만 존재 (실행 시 메모리 위치에 바인딩 X)

(컴파일러가 컴파일 할 때만 심볼테이블에 저장해두고 상수를 값으로 교체)

 

2) 할당

- 이름에 메모리 위치가 바인딩되어 프로그램의 환경이 구성되는 작업을 지칭

- 전역 변수들은 정적으로 할당됨

  지역 변수들은 해당 블록이 실행될 때 동적으로 할당되었다가 끝나면 할당 해제됨

-> 스택과 유사하게 동작

 

- 함수의 호출 = 활성화

- 함수가 활성화 될 때 할당된 메모리 = 활성 레코드

(함수의 지역변수들을 위한 메모리 할당)

 

3) 수명

- 환경에서 선언 처리의 결과로 생기는 할당된 메모리의 구역 = 객체

- 객체의 수명 -> 환경에서 할당되어 있는 기간 (시간적 개념)

(scope-> 공간적 개념)

: 메모리에 할당된 시점부터 해제될 때까지의 기간

 

- 프로그램에서 변수가 유효한 범위의 영역이 실행되는 동안은 그 변수를 위한 메모리 영역이 유지되다가 영역을 벗어났을 때 메모리가 해제되는 것이 좋다

- 객체의 수명이 참조 가능한 영역을 넘어서거나 객체 수명을 넘어서 참조 가능하면 문제

 

어떤 할당이 더 효율적인가?

- 정적 할당

- 모든 종류의 바인딩에서 정적 바인딩이 동적 바인딩보다 효율적 (실행시간 전에 한번만 하면 됨)

- 동적 바인딩이 정적보다 유연함(바인딩이 실행시간으로 미뤄지므로)

 

  -> FORTRAN: 효율성 중시

  컴파일 과정에서 필요한 메모리 총량을 분석하여 프로그램이 적재될 때 메모리에 환경 구성을 완료한다.

  (메모리 다루는 것에 CPU 시간 낭비가 없음)

 

전역 변수는 실행 전에 정확히 필요한 만큼만 할당이 가능하다.

-> 모든 변수를 정적으로 할당하면 안됨

(전역변수, 지역변수, 포인터)

모든 변수를 정적으로 할당하면 재귀, 동적 자료구조 사용이 불가능하다.

factorial() 재귀 함수

변수가 정적으로 할당된 경우

-> 재귀호출되는 함수의 매개변수는 호출될 때마다 별도의 메모리 공간을 가져야 함

정적으로 할당되면 한 메모리 공간을 재사용하게 되어 이전 값을 참조할 수 없다.

- 컴파일러가 함수가 몇번 호출되는지 예측할 수 없다.

 

☆ 정적 바인딩이 항상 효율적이다.

-> 프로그램이 수행되기 위해 필요한 메모리 총량을 실행전에 미리 파악할 수 있다면 미리 할당하는 것이 좋다.

=> 메모리 할당은 CPU가 관리, 미리 다 해두면 CPU의 오버헤드도 없다.

★ but, 필요한 메모리 총량을 미리 알 수 없음

-> 어차피 미리 알 수 없어 실행시간에 메모리 관리 필요

- 그러므로 아직 활성화되지 않은 block의 지역변수를 미리 할당할 필요가 없다

-> 필요한 시기에 메모리 할당 & 해제

 

선택

재귀 호출 허용 VS 모든 변수 정적 할당

- 재귀 호출이 중요한 제어 추상화 매커니즘, 편의성

 

★ 스택에 포인터 변수의 동적 할당은 안됨

스택에 포인터 변수의 공간을 할당하면, 스택은 block이 끝나면 활성레코드도 pop되어야 하는데 해당 변수가 스택 TOP에 없게 됨

 

# 기억장소 부류

- 메모리 위치가 바인딩되는 할당 시점에 따라 구분

1) 정적

- 전역변수

- 메모리의 전역 구역

 

2) 동적

1> 자동 / 스택 기반

- 지역변수

- 메모리의 스택 구역

(해당 블록이 활성화 될 때)

컴파일 시간에 함수가 활성화되면 필요한 활성레코드의 사이즈 정보를 컴파일러가 파악

 

2> 수동 / heap 기반

- 포인터 변수

- 메모리의 heap 구역

(메모리를 동적으로 할당하는 코드 실행 시 바인딩)

 

3.7 변수와 상수

1) 변수

- 저장하고 있는 값이 수행되는 동안에 바뀔 수 있는 객체

주요 속성: 할당, 영역(메모리 위치, 저장된 값)

- 이름, 메모리 위치, 값, 데이터 타입, 저장공간 크기

★변수는 메모리에 바인딩 되어야 한다.

 

< 배정문 >

- 변수의 값을 바꾸는 기본적인 방법

- 모든 언어에 존재

 

x = y;  (일률성 x)

① 복사에 의한 배정 

- 우변의 식의 값을 좌변의 위치에 복사

(좌변이 가리키는 위치에 우변의 값을 복사)

- 기억장소 의미론, 값 의미론

 

- 우변 = 값 r-value, 메모리에 저장된 값을 나타냄

- 좌변 = 위치 l-values, 변수에 연결된 주소를 나타냄

 

② 공유에 의한 배정

- 우변의 위치가 좌변으로 복사됨

- x, y는 별칭

 

구조체, 배열, 클래스는 복사에 의한 배정을 하는 경우 복사할 값이 많아짐 (공유가 효율)

 

- 얕은 복사

포인터의미론, 참조 의미론

- 복사 효율성 & 원치 않은 부수적 효과

** X를 바꾸면 Y 도 값이 바뀜

-> 프로그램에 숨겨진 의미가 존재 -> 가독성 떨어짐

 

표준적 구현

- *포인터와 묵시적인 *참조인출을 이용

- 주소를 복사

 

③ 복제에 의한 배정

- 깊은 복사

- 새로운 위치에 우변의 값을 복사

 

2) 상수

- 프로그램 전반에 걸쳐 고정된 값을 가지는 개체

- 변수와 유사, 위치 속성은 부재 (변하지 않으므로 컴파일 시 값으로 바뀜 -> 메모리 필요 없음)

[종류]

- 리터럴(실제 값), 현시 상수 PI

상수가 바인딩 되는 시점

- 컴파일 시간 상수

- 정적 상수

- 동적 상수

(프로그래밍언어마다 상수 허용 범위가 다름)

 

3.8 별명, 허상 참조, 쓰레기

1) 별명

- 함수 호출, 포인터 변수, 공유에 의한 배정 등에 의해 발생

<위험성>

- Alias는 side effect를 야기

- 문장 수행 이후에도 유지되는 변수 값의 변화

- 모든 부작용이 나쁜 것이 아님, 배정문은 그것을 일으키도록 의도된 것

- 잠재적 해로움

 

2) 허상 참조

- 환경에서 할당 해제되었지만 프로그램에서 여전히 접근 가능한 경우 (허상을 가리킴)

- 한 객체에 대하여 환경에서 수명을 넘어 참조

- 포인터가 할당 해제된 객체를 가리킬 때 발생

 

Java는 허상 참조 발생하지 않음

- 명시적인 포인터 부재

- 주소 연산자& 부재

- 메모리 할당 해제 연산자 free, delete 부재

 

<위험성>

- 틀린 결과

- 메모리에 있는 다른 프로그램을 오염

- 원인 찾기 힘든 실행 오류

*허상 참조가 매우 위험 -> 명시적 메모리 할당 해제 연산자를 없앰

대신 쓰레기를 자동으로 수거 ->CPU 오버헤드

(자바 Garbage collection)

 

3) 쓰레기

- 환경 내에 할당, 더 이상 접근 불가

- 메모리 해제하기 전, 다른 주소를 넣으면 이전의 주소는 참조 불가, 반납 안됨

* 쓰레기가 많아지면 메모리 부족으로 실행 중단될 수 있음

 

 

반응형
Comments