명령 사이클(Instruction Cycle)

CPU가 명령어를 수행하는 과정을 의미한다. 일반적으로 명령 사이클은 여러 단계로 이루어져 있다.

1. 인출(Fetch)

메모리에서 프로그램 카운터에 저장된 주소의 명령어를 가져온다. 그 다음 프로그램 카운터를 다음 명령어의 주소로 업데이트한다.

 

2. 디코딩(Decode)

명령어를 분석하여 해당하는 작업을 파악하고 필요한 피연산자를 결정한다.

 

3. 실행(Execute)

분석된 명령어와 연관된 작업을 수행하고 결과를 생성한다.

 

4. 저장(Store)

연산 결과를 레지스터 또는 메모리에 저장한다.

 

CPU는 위 사이클을 프로그램이 실행되는 동안 반복하며 각 명령어가 수행되는 순서대로 진행한다.

 

명령어 집합 구조(Instruction Set Architecture, ISA)

컴퓨터의 하드웨어와 소프트웨어 사이의 인터페이스를 정의한다. ISA는 하드웨어가 지원하는 명령어들의 집합, 데이터 유형, 레지스터, 메모리 주소 지정 방식, 입출력 관리 등에 대한 규칙과 규격을 제공한다. ISA는 컴퓨터 아키텍처의 핵심 구성 요소로 컴파일러와 운영 체제가 하드웨어와 상호 작용하는 방식을 결정한다. 컴퓨터는 다양한 아키텍처가 존재하며 각각의 아키텍처는 고유한 ISA를 가지고 있다.

 

ISA는 프로세서와 소프트웨어 간의 인터페이스 역할을 한다.

  • 명령어 집합 : 프로세서가 인식하고 수행할 수 있는 기본 명령어 집합
  • 레지스터 파일 : 프로세서 내부에 있는 레지스터들의 집합
  • 메모리 접근 방식 : 프로세서가 메모리에 접근하는 방법 정의
  • 입출력 방식 : 프로세서와 외부 장치 간의 데이터 교환 방식을 정의
  • 인터럽트 및 예외 처리 : 프로세서가 예외 상황이 발생했을 때 처리하는 방식 정의

 

 

 

 

728x90
반응형

'Computer > Engineering' 카테고리의 다른 글

데이터 경로와 제어 유닛  (0) 2023.03.17
메모리  (0) 2023.03.17
레지스터  (0) 2023.03.17
CPU  (0) 2023.03.17
튜링 머신 : 컴퓨터 과학의 기초  (0) 2023.01.07

Register

CPU내에 위치한 기억장치로 특정 목적을 위해 사용하는 저장소이다.

매우 빠른 속도로 데이터를 읽고 쓰는 특징이 있다. CPU의 성능에 큰 영향을 미치며 레지스터의 개수와 크기가 높을수록 빠른 처리 속도를 기대할 수 있다.

 

종류

Accumulator Register(AC)  

누산기 레지스터

산술 및 논리 연산에 사용되는 값을 저장하는 레지스터이다.

이 레지스터는 CPU의 연산 결과를 저장하거나 결과를 다음 연산에 사용하기 위해 일시적으로 저장하는 역할을 한다.

대부분의 CPU에서 누산기 레지스터는 덧셈, 뺄셈, 곱셈, 나눗셈 등의 산술 연산을 수행하며, 비트 논리 연산을 수행하기도 한다. 이러한 연산의 결과는 다시 누산기 레지스터에 저장되어 다른 연산에 사용될 수 있다.

 

이 레지스터는 초기 컴퓨터 아키텍처부터 현대 컴퓨터 시스템에서까지 사용되고 있다. 하지만 현대에는 더 많은 범용 레지스터와 복잡한 레지스터 구조를 가지고 잇기 때문에 누산기 레지스터의 중요성은 상대적으로 감소했다. 

 

누산기 레지스터는 CPU내에 위치한 특별한 레지스터로 산술 및 논리 연산을 수행하고 그 결과를 저장하는 데 사용된다. 이는 연산 결과를 다른 명령어에 전달하거나 다음 연산에 사용하기 위한 중요한 구성요소이다.

 

Program Counter(PC)

프로그램 카운터

다음에 실행될 명령어의 메모리 주소를 저장하고 관리하는 일종의 포인터 역할을 한다. CPU는 프로그램 카운터에 저장된 주소를 사용하여 메모리에서 해당 명령어를 가져와 실행하며 명령어 실행이 완료되면 프로그램 카운터는 다음 명령어의 주소로 자동 업데이트된다.

 

일반적으로 프로그램 카운터는 순차적으로 실행되는 명령어들의 주소를 저장하고 있지만 제어 전달 명령어분기(Branch)점프(Jump) 명령어가 사용되면 프로그램 카운터가 다른 메모리 주소로 변경되어 명령어의 실행 순서가 바뀔 수 있다. 이러한 동작은 조건부 실행, 함수 호출, 반복문 및 예외 처리 등 다양한 프로그래밍 구조를 구현하는데 필수적이다.

 

- 제어 전달 명령어(Control Transfer Instructions) : 프로그램의 실행 흐름을 변경하는 명령어이다. 프로그램은 일반적으로 순차적으로 실행되지만, 조건부, 반복문, 함수 호출 등 다양한 프로그래밍 구조를 구현하기 위해서는 실행 흐름을 변경할 필요가 있다. 이때 사용되는 명령어들을 제어 전달 명령어라 하며 크게 분기, 점프, 서브루틴 호출 세 가지 유형으로 분류된다. 

- 분기(Branch) : 주어진 조건이 참인지 거짓인지에 따라 프로그램의 실행 흐름을 변경한다. 일반적으로 조건문 또는 반복문에서 사용된다. 분기 명령어는 조건에 따라 프로그램 카운터를 업데이트하여 실행 흐름을 변경한다.

- 점프(Jump) : 프로그램의 실행 흐름을 명시적으로 변경한다. 점프 명령어는 프로그램 카운터를 새로운 주소로 업데이트하여 실행 흐름을 변경한다. 점프는 조건 없이 변경하는 것과 조건에 따라 변경하는 두 가지 유형이 있다.

- 서브루틴 호출(Subroutine Call) : 함수 호출과 반환에 사용된다. 함수를 호출할 때 실행 흐름은 함수의 시작 주소로 이동하고 함수의 실행이 완료되면 원래 실행 흐름으로 돌아와야 하는데 이때 사용되는 명령어가 서브루틴 호출 명령어이다.

 

프로그램 카운터는 다음에 실행할 명령어의 메모리 주소를 저장하는 CPU의 특수한 레지스터로 명령어가 순차적으로 실행되거나 분기 및 점프 명령어에 의해 변경되는 경우에도 명령어의 실행 순서를 관리하고 제어기 때문에 다양한 프로그래밍 구조를 구현하기 위해 필수적인 구성 요소이다.

 

Temporary Register(TR)

임시 레지스터

데이터를 일시적으로 저장하기 위해 사용되는 레지스터이다. 연산 과정에서의 중간 결과를 저장하거나, 데이터 전송, 레지스터 간의 값을 교환하는 등의 작업에 사용된다. 임시 레지스터는 데이터를 보유하기 때문에 포함된 비트 수는 데이터 워드의 비트 수와 같다.

 

- 워드(word) : 메모리에서 레지스터로 데이터를 옮기거나 ALU를 통해서 데이터를 조작할 때 하나의 명령어로 실행될 수 있는 데이터 처리 단위이다.

 

CPU 아키텍처에 따라 다양한 이름과 기능을 가질 수 있는데 예를 들어 x86 아키텍처에서는 범용 레지스터라고 불리는 레지스터들이 있으며, 이 레지스터들은 연산, 데이터 전송, 주소 계산 등 다양한 목적으로 사용된다. 이러한 범용 레지스터들은 EAX, EBX, ECX, EDX 등과 같은 이름으로 구분되며 종종 중간 결과를 저장하는 임시 레지스터로 사용된다. 범용 레지스터들의 이름과 종류는 아키텍처마다 다르다.

 

ARM 아키텍처에서는 R0부터 R12까지의 범용 레지스터가 있으며, 이 레지스터들 역시 데이터를 임시로 저장하고 연산에 사용된다. 

 

임시 레지스터는 프로그램 실행 중 발생하는 다양한 작업을 처리하는데 필수적인 레지스터로 중간 연산 결과를 저장하거나 데이터를 전송하는 등의 작업을 수행하기 위해 사용되며, CPU 아키텍처와 디자인에 따라 여러 가지 이름과 기능을 가진다.

 

Instruction Register(IR)

명령어 레지스터

실행 중인 명령어를 저장하는 데 사용된다. 프로그램 카운터에 저장된 주소를 통해 메모리에서 명령어를 가져온 후, 해당 명령어는 명령어 레지스터에 저장되어 실행을 위한 처리가 진행된다.

 

명령어 레지스터는 CPU가 현재 어떤 명령어를 실행하고 있는지 파악하는 데 사용되며, 명령어의 인코딩 된 정보를 저장하고 있다. 이 정보는 오퍼레이션 코드, 피연산자 주소, 상수 값 등으로 구성되어 있을 수 있고 CPU는 명령어 레지스터의 정보를 해독하여 적절한 연산을 수행하고 결과를 다른 레지스터에 저장하거나 메모리로 전송한다.

 

- 오퍼레이션 코드(opcode) : 명령어가 수행할 연산의 종류를 나타내는 코드이다. CPU는 이 코드를 해석하여 어떤 작업을 수행해야 하는지를 결정한다. 예를 들어 덧셈, 뺄셈, 곱셈 등의 산술 연산, 논리 연산 데이터 전송, 제어 전달 등의 작업은 각각 고유한 오퍼레이션 코드를 가진다. 오퍼레이션 코드는 명령어의 일부분으로 명령어의 길이와 아키텍처에 따른 다른 크기를 가질 수 있다.

- 피연산자(operand)  주소 : 연산의 대상이 되는 데이터를 나타낸다. 피연산자 주소는 이러한 데이터가 저장된 메모리 주소나 레지스터를 가리킨다. 명령어는 하나 이상의 피연산자를 가질 수 있으며 이들의 주소 정보를 함께 저장하고 있다. 피연산자 주소는 명령어 내에서 오퍼레이션 코드 뒤에 위치하며 주소 지정 방식(Addressing Mode)에 따라 다양한 형태를 가질 수 있다. 주소 지정 방식은 피연산자의 메모리 주소나 레지스터를 참조하는 방법을 정의하며, 아키텍처에 따라 다양한 방식이 사용된다.

 

명령어 레지스터는 현재 실행 중인 명령어를 저장하고 있는 CPU의 특수한 레지스터이다. 이 레지스터는 명령어를 실행하는 동안 해당 명령어의 인코딩 된 정보를 저장하고 관리하며, CPU가 적절한 작업을 수행할 수 있도록 한다.

 

Data Register(DR)

데이터 레지스터

주로 데이터를 저장하고 연산에 사용되는 목적으로 사용된다. CPU와 메모리 사이에서 데이터를 전송하거나, 연산의 피연산자와 결과를 저장하는 데 사용되는 레지스터이다. 

 

데이터 레지스터는 일반적으로 범용 레지스터라고 불리며 이러한 레지스터가 다양한 종류의 데이터를 저장하고 연산에 사용될 수 있는데 반해 다른 특수 레지스터들은 주로 특정 목적으로 사용된다. 

 

데이터 레지스터는 CPU내부에서 데이터를 저장하고 연산에 사용되는 레지스터로, 범용 레지스터라고 불린다. 이러한 레지스터는 CPU와 메모리 사이에서 데이터를 전송하거나, 연산의 피연산자와 결과를 저장하는데 사용된다.

 

Address Register(AR)

주소 레지스터

주로 메모리 주소를 저장하고 관리하는 데 사용된다. 주소 레지스터는 메모리에서 데이터를 읽거나 쓰는데 필요한 메모리 주소를 담당하며 주소 계산, 포인터 연산, 배열 인덱싱 등 다양한 작업에서 사용된다.

 

종류

  • 베이스 레지스터(Base Register) : 메모리 주소 영역의 시작 주소를 저장하는 데 사용된다.
  • 인덱스 레지스터(Index Register) : 메모리 주소 영역 내에서 특정 위치를 가리키기 위해 사용된다. 주로 배열 인덱싱에 활용된다.
  • 세그먼트 레지스터(Segment Register) : 일부 아키텍처에서만 사용되며 메모리 세그먼트의 기준 주소를 저장하는 레지스터로 주소 계산에 사용된다.
  • 스택 포인터 레지스터(Stack Pointer Register) : 스택의 최상단을 가리키는 주소를 저장한다.

세그먼트(Segment)

컴퓨터 메모리를 관리하는 방법 중 하나로 메모리를 의미상 관련 있는 덩어리로 나누는 것을 의미한다. 프로그램이 메모리에 로드될 때 메모리 공간을 논리적으로 구분하여 관리하며, 각 세그먼트는 시작 주소와 길이로 정의된다. 

세그 먼트는 메모리 보호와 관련된 장점을 가지며 다른 세그먼트 간에 데이터의 접근을 제한하여 메모리 충돌을 방지하고 프로그램의 안정성을 높인다. 

 

세그먼트 종류

코드 세그먼트(Code Segment) : 프로그램의 코드를 저장하는 세그먼트로 일반적으로 읽기와 실행 권한만 가지며 쓰기 권한은 없다. 이를 통해 코드 영역의 데이터 오염을 방지한다.

데이터 세그먼트(Data Segment) : 전역 변수와 정적 변수를 저장하는 세그먼트로 일반적으로 읽기와 쓰기 권한이 있다. 프로그램의 수명 동안 고정된 위치에 데이터를 저장하는 데 사용된다.

스택 세그먼트(Stack Segment) : 함수 호출과 지역 변수를 저장하는 데 사용되는 스택 자료구조를 위한 세그먼트이다. 스택 포인터를 사용하여 현재 스택의 최상단을 가리키며 함수 호출 시 스택 프레임이 할당되고 반환 시 해제된다.

힙 세그먼트(Heap Segment) : 동적으로 할당되는 메모리를 저장하는 세그먼트로 프로그램이 실행되는 동안 메모리를 필요에 따라 할당하고 해제하는 데 사용된다.

 

일부 프로세서 아키텍처에서는 주소 레지스터를 별도로 두기도 하지만, 다른 아키텍처에서 범용 레지스터를 주소 레지스터로 사용하기도 한다. 예를 들어 x86 아키텍처에서는 ESI와 EDI 등의 범용 레지스터가 주소 레지스터로 사용되며 이를 통해 메모리 주소를 저장하고 참조할 수 있다.

 

다양한 종류의 주소 레지스터는 메모리 주소를 저장하고 관리하는 데 사용되고 아키텍처에 따라 주소 레지스터와 범용 레지스터가 별도로 존재할 수 있으며 범용 레지스터가 주소 레지스터의 역할을 수행할 수도 있다.

 

Input Register(INPR)

입력 레지스터

주로 외부 장치에서 전달되는 데이터를 저장하는 데 사용된다. 이 레지스터는 입력 데이터를 저장하고 CPU가 처리할 수 있는 형태로 전달하는 역할을 한다. 

 

일반적으로 입력 레지스터는 입출력 시스템의 일부로 동작하며 외부 장치로부터 데이터를 받아올 때 해당 데이터를 일시적으로 저장하는 공간으로 사용된다. CPU는 입력 레지스터에 저장된 데이터를 읽어서 적절한 처리를 수행한 후 결과 데이터를 다른 레지스터 또는 메모리 영역에 저장할 수 있다.

 

입력 레지스터의 크기는 아키텍처에 따라 다를 수 있으며 일반적으로 8비트, 16비트, 32비트 등의 크기를 가질 수 있다. 입력 레지스터의 크기는 처리할 수 있는 데이터의 크기를 결정하며, 레지스터의 크기에 맞춰 외부 장치에서 전달되는 데이터의 크기가 정해진다.

 

입력 레지스터는 외부 장치와 CPU 간의 데이터 전달을 위한 중간 저장소 역할을 하며, CPU가 외부 장치에서 전달되는 데이터를 처리할 수 있도록 돕는 중요한 구성요소이다.

 

Output Register(OUTR)

출력 레지스터

CPU에서 처리된 데이터를 외부 장치로 전달하기 전에 일시적으로 저장하는 데 사용된다. 출력 레지스터는 CPU에서 생성된 결과 데이터를 외부 장치로 전송하는 과정에서 중간 저장소 역할을 한다.

 

일반적으로 출력 레지스터는 입출력 시스템의 일부로 동작하며 CPU가 처리한 데이터를 외부 장치로 전송할 때 해당 데이터를 일시적으로 저장하는 공간으로 사용된다. CPU는 처리한 데이터를 출력 레지스터에 저장한 후 컨트롤러를 통해 해당 데이터를 외부 장치로 전달할 수 있다.

 

출력 레지스터의 크기는 아키텍처에 따라 다를 수 있으며 일반적으로 8비트, 16비트, 32비트 등의 크기를 가질 수 있다. 출력 레지스터의 크기는 전달할 수 있는 데이터의 크기를 결정하며 레지스터 크기에 맞춰 CPU에서 처리된 데이터의 크기가 정해진다.

 

출력 레지스터는 CPU와 외부 장치 간의 데이터 전달을 위한 중간 저장소 역할을 하며, CPU가 처리한 데이터를 외부 장치로 전달하는데 필요한 중요한 구성 요소이다.

 

728x90
반응형

'Computer > Engineering' 카테고리의 다른 글

데이터 경로와 제어 유닛  (0) 2023.03.17
메모리  (0) 2023.03.17
명령 사이클과 명령어 집합 구조  (0) 2023.03.17
CPU  (0) 2023.03.17
튜링 머신 : 컴퓨터 과학의 기초  (0) 2023.01.07

CPU

컴퓨터 시스템을 구성하는 요소는 다양하며, 각각의 구성요소들이 상호 작용을 통해서 전체 시스템이 동작하게 된다.

그 중 중앙 처리 장치(CPU, Central Processing Unit)는 컴퓨터 시스템에서 가장 중요한 부품 중 하나이다.

 

구성요소

연산 유닛(Arithmetic Logic Unit, ALU)

산술 및 논리 연산을 수행한다. 덧셈, 뺄셈, 곱셈, 나눗셈 같은 산술 연산과 AND, OR, NOT, XOR과 같은 논리 연산을 처리한다.

 

제어 유닛(Control Unit, CU)

메모리에서 명령어를 가져와 해독하고 실행한다. 또한 다른 구성요소들을 제어하여 명령어 실행에 필요한 데이터를 제공하거나 결과를 저장한다.

 

레지스터(Register)

CPU 내부의 작은 기억장치이다. 매우 빠른 접근 속도를 가지고 연산에 필요한 데이터를 저장하거나 중간 결과를 저장하는데 사용된다. 종류는 크게 범용, 프로그램 카운터, 명령, 주소 레지스터 등이 있다.

 

캐시 메모리(Cache Memory)

캐시 메모리는 CPU와 주 기억장치 사이에 위치하며 자주 사용되는 데이터와 명령어를 빠르게 접근할 수 있도록 저장한다. 캐시 메모리는 CPU 성능을 향상시키는 데 중요한 역할을 한다.

 

명령어 주기

CPU는 명령어 주기를 통해 작동한다.

1. 인출(Fetch) : 메모리에서 명령어를 가져오는 단계

2. 해독(Decode) : 가져온 명령어를 해독하여 실행할 동작을 결정하는 단계

3. 실행(Execute) :  해독된 명령어를 실행하는 단계

4. 저장(Store) :  실행 결과를 레지스터나 메모리에 저장하는 단계

 

성능

CPU의 성능은 클럭 속도, 코어 수, 캐시 크기, 명령어 집합 구조(ISA) 등 다양한 요소에 의해 영향을 받는다. 클럭 속도는 CPU가 초당 처리할 수 있는 명령어의 수를 나타내며, 코어 수는 동시에 처리할 수 있는 명령어 스트림의 수를 의미한다. 캐시 크기는 CPU와 메모리 사이에 위치한 고속 메모리의 용량을 나타내며, 명령어 집합 구조는 CPU가 어떤 종류의 명령어를 처리할 수 있는지를 결정한다.

 

다중 코어 프로세서와 파이프라이닝, 병렬 처리 등의 기술을 통해 성능을 향상시킬 수 있으며 이러한 기술은 동시에 여러 작업을 처리하거나 여러 단계로 구성된 작업을 동시에 진행함으로써 전체 시스템의 처리 속도를 높일 수 있다.

728x90
반응형

'Computer > Engineering' 카테고리의 다른 글

데이터 경로와 제어 유닛  (0) 2023.03.17
메모리  (0) 2023.03.17
명령 사이클과 명령어 집합 구조  (0) 2023.03.17
레지스터  (0) 2023.03.17
튜링 머신 : 컴퓨터 과학의 기초  (0) 2023.01.07

Visual Studio 2022

 

class 생성

멤버변수 int 하나

public class Test
{
	int data_1;
}

public static void Main(string[] args)
{
	Test test = new Test();
}

C# Class Size Test (Field int 1)

int 하나인데 크기가 24 바이트이다.

int = 4byte 인데 왜 6배나 되는걸까?

=>6개 이상일 때 크기 확인 해보기로함

public class Test
{
	public int data_1;
	public int data_2;
	public int data_3;
	public int data_4;
	public int data_5;
	public int data_6;
}

일단 int 6개로 테스트, 기대 결과는 그대로 24byte

C# Class Size Test (Field int 6)

이게 무슨일이지, 40 byte 가 나옴

처음 결과를 기준으로 생각해보면 24 + 16 즉 16 byre가 추가됨.

4(int) * 4(count) 라고 생각해보고 다시 필드를 int * 3으로 테스트

 

public class Test
{
    public int data_1;
    public int data_2;
    //public int data_3;
    //public int data_4;
    //public int data_5;
    //public int data_6;
}

예상한대로면 처음 결과인 24가 나와야함

C# Class Size Test (Field int 2)

결과는 기대한대로 나왔지만 여전히 24라는 크기가 이해가 안감,

혹시나해서 클래스를 인스턴스화 안했을때 한번 확인해봤지만 당연하게 해당 메모리 안잡힘

 

이번엔 하나씩 필드 늘려가면서 확인해보기로함

C# Class Size Test (Field int 3)

int * 4 일때는 동일하게 32

C# Class Size Test (Field int 5)

int * 5 부터 40 으로 잡혔음

 

그럼 그냥 빈 클래스는 얼마로 잡히나 확인했는데

애초에 비어있는 클래스를 인스턴스하면 24 byte 할당됨

여기서 2개씩 int 추가될 때마다 8 byte가 늘어나는데 이 늘어나는 값은 4(int) * 2(count) 맞음

 

비어있는 클래스가 왜 24 byte의 힙을 차지하는지 파악해보면될듯함

일단 Program 외부에 Test_2 클래스, 내부에는 Test_1 선언 후 비교

public class Test_2 { }
public class Program
{
    public class Test_1 { }
    public static void Main(string[] args)
    {
        Test_1 test_1 = new Test_1();
        Test_2 test_2 = new Test_2();
    }
}

C# Class Size Test_1 / Test_2 (empty)

테스트 해본 결과 클래스를 할당하면 기본 크기가 24로 잡히고 이 안에 8byte 까지는 포함시킬 수 있는걸로 보인다.

즉 기본 24 = 16(사용됨) + 8(여유 공간) 이고 크기가 24 byte를 넘을 경우 8byte 단위로 확장되는걸로 파악됨

 

구글링 해본 결과 내가 원하는 정보를 찾을 수 있었다.

 

OF MEMORY AND STRING_Jon Sket

 

본래 글의 취지는 .Net에서 ASCII 문자열의 효율적인 표현에 관한것에 대한 답이다.

위 글에서는 x86과 x64를 모두 비교하고 있는데 이 글에서는 x64를 기준으로 정리한다.

 

그럼 이제 왜 0byte는 될수없는지 그리고 24byte 중 사용할 수 없는 16byte와 사용 가능한 8byte는 무엇 때문인지 정리한다.

 

우선 C#에서 모든 타입은 object 타입을 베이스로한다. 그리고 이 object 타입의 크기는 24byte이다.

이 24byte의 크기는 16byte의 오버헤드와 참조를 위한 8byte로 이루어진다. 

 

오버헤드란 처리를 하기 위해 들어가는 간접적인 처리 시간, 메모리 등을 말한다. 즉 C#의 object를 메모리에 유지시키는데 필요한 메모리 양이 16byte이며 .Net의 CLR은 데이터가 없는 object에서 작업할 수 없기 때문에 오버헤드만 가지고는 object를 사용할 수 없고 여유 공간이 필요하며 이 크기가 8byte이다. 따라서 선언될 수 있는 가장 작은 크기의 메모리는 24byte가 된다.

 

이 중 여유공간인 8byte는 실제 데이터를 저장할 수 있기 때문에 int가 2개 포함된 class역시 24byte였다. 이 8byte는 전체를 사용할 수 있는 공간이기 때문에 1byte로 8를 사용하는것도 가능하다. 그리고 int가 3개가 될때 24byte에서 8byte로 증가했는데 이것은 메모리의 경계인 패딩의 크기가 8byte였기 때문이다. 

 

class 역시 object 타입을 베이스로 하고 있고 빈 class를 인스턴스화 하는것은 object를 생성하는것과 마찬가지이기 때문에 빈 class가 24byte라는것도 이제 납득이 간다. 

 

오버헤드에 대한 내용

overhead - Andrew Hunter
overhead - Stack Overflow

 

왜 이렇게 많은 공간이 필요한지 또 이 공간은 어떤식으로 사용되는지 궁금한데 이 부분은 더 자세히 찾아봐야 될것같다.

 

정리

64bit 기준으로 

1. 비어있는 class 또한 하나의 object이다. 따라서 24byte 메모리를 차지한다.

2. 이 메모리는 16byte의 내부 데이터 참조형으로 사용되는 오버헤드와 8byte의 여유공간으로 구성된다.

3. 여유공간은 모두 사용이 가능하며 메모리는 8byte의 패딩을 가지고 있다.

 

728x90
반응형

'Memo' 카테고리의 다른 글

다국어를 제공하는 방법에 대해서  (0) 2023.05.10

Interoperability(Interop)

상호운용성

unmanaged code에 대한 기존 투자를 보존하고 활용할 수 있게 한다.

즉 CLR을 사용하지 않는 어셈블리를 CLR에서 사용할 수 있게 만드는 것이다.

interop은 managed와 unmanaged를 오고가는 메모리 비용과 코드 작성 비용 때문에 최소화하는게 좋다.

 

COM Interop

Component Object Model(COM) 을 사용하면 개체의 기능을 다른 컴포넌트와 윈도우 플랫폼의 호스트 프로그램에 사용할 수 있다. 사용자가 기존 코드 베이스와 상호 운용할 수 있도록 .Net 프레임워크에서 COM라이브러리를 통해 interop을 지원한다.

 

Platform Invoke(P/Invoke)

P/Invoke는 사용자의 managed code에서 unmanaged 라이브러리의 구조, 콜백 그리고 기능에 접근할 수 있도록 한다. 대부분 P/Invoke API는 SystemSystem.Runtime.InteropServices 두 개의 네임스페이스를 포함한다.

이 두개의 네임스페이스를 사용하면 네이티브 컴포넌트를 사용할 수 있는 도구가 제공된다.

using System;
using System.Runtime.InteropServices;

public class Program
{
	// Import dll (containing the funtion)
    [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)
    // Define
    private static extern int MessageBox(IntPtr hWnd, string IpText, string IpCaption, uint uType);
    
    public static void Main(string[] args)
    {
    	// Invoke the function as a regular managed method
        MessageBox(IntPtr.Zero, "Command-line message box", "Attention!", 0);
    }
}
  1. Attribute
    • [DllImport] attribute은 런타임에 unmanaged DLL을 로드할것을 알린다.
    • "user32.dll" 문자열은 사용할 기능이 포함된 DLL을 대상으로 한다.
    • CharSet = CharSet.Unicode 문자열을 Mashalling하는데 사용할 문자 집합을 지정한다.
    • SetLastError = true 런타임에서 오류가 발생했을때 사용자가 Mashal.GetLastWin32Error()를 통해서 에러코드를 감지할 수 있게한다.
  2. Declare
    • 메서드 선언시 메서드명은 사용할 unmanaged code와 동일한 이름으로 작성한다.
    • 이때 extern 키워드를 통해서 해당 메서드가 외부의 메서드임을 런타임에 알린다.
    • 런타임은 외부 메서드라는것을 알게되면 해당 managed 메서드를 호출할 때 attribute의 설정을 통해 특정된 DLL을 찾아서 실행시킨다.
  3. Main method
    • managed code 즉 현재 작업중인 코드에서 extern으로 선언한 메서드를 호출하여 외부 메서드를 사용한다. 

 

Managed C++(C++/CLI)

Managed Extensions for C++ 또는 Managed C++은 문법적 그리고 구문적 확장, 키워드와 속성을 포함하고 C++의 구문 및 언어를 가져오는 .Net Framework의 C++ 언어 확장 집합이다. 이 확장자는 managed code상에서 C++ 코드가 CLR의 대상이 될 수 있도록 그리고 지속적으로 native code와 상호 운용될 수 있게하려고 마이크로소프트에서 만들었다.

 

2004년 Managed C++은 구문을 명확하고 단순화하고 managed generics을 포함하도록 기능을 크게 개선시켰다. 이렇게 새로운 확장자는 C++/CLI로 지정되었고 Visual Studio 2005 이후에 포함되면서 Managed C++을 대체하였다.

 

SWIG

Simplefied Wrapper and Interface Generator

C 또는 C++로 작성된 컴퓨터 프로그램이나 라이브러리 다른 프로그래밍 언어에서 사용할 수 있도록 연결하는데 사용하는 오픈소스 도구이다. 

주목적은 네이티브 함수의 호출, 복잡한 자료형을 함수에 전달, 메모리 부적절하게 해제하지 못하게 방지, 언어 간에 오브젝트 클래스를 상속할 수 있게 하는것이다.

728x90
반응형

Managed code

.Net Framwork에서 CLR(Common Language Runtime)의 제어하에서 실행되는 코드를 말한다.

이 코드는 Visual Basic.Net이나 C#과 같은 .Net Framework를 지원하는 언어의 컴파일러를 통해서 만들어지는 코드로 컴파일러에 의해 IL(Intermediate Language)이라는 중간 언어로 생성된다. IL은 컴퓨터에서 바로 실행할 수 있는 기계 언어가 아니며 사용자가 생성한 코드의 클래스, 메서드, 속성 등을 나타내는 메타데이터와 함께 어셈블리라는 파일로 저장된다.

 

CLR은 이러한 어셈블리를 실행할 때 코드의 보안, 메모리 관리, 스레딩같은 관리를 담당하는 다양한 서비스를 제공하며 실행 시점에 필요한 코드는 JIT 컴파일로 컴퓨터의 환경에 맞는 기계어로 변환되어 실행된다. 이런 특징 때문에 CLR은 Managed Program이라고도 불리며  Managed Code를 사용하기 때문에 이러한 동작이 가능하다.

 

unmanaged code

CLR 외부에서 실행되는 코드

unmanaged code는 Visaul Studio.Net 2002가 나오기 전에 만든 코드를 말한다. 즉 Visual Basic 6, Visual C++6 과 같은 컴파일러는 unmanaged code를 생성한다. 이 컴파일러들은 managed code처럼 IL 생성과정없이 컴파일이 수행되는 해당 컴퓨터에 적합한 기계 코드를 생성해 낸다. 이렇게 생성된 기계 언어는 동일한 구성의 다른 컴퓨터에서 실행될 수 있을지도 모르지만 다른 구성의 컴퓨터의 경우 실행이 불가능하다. 또한 이렇게 생성된 unmanaged code는 보안 및 메모리 관리 서비스를 실행시에 Runtime으로부터 받을 수가 없고 대신에 COM call의 서비스를 통해 받을 수 있다.

 

정리하자면 managed code의 경우 runtime이 알아서 메모리 관리를 해주는데 반해 unmanaged code의 경우, 사용자가 관리해야하지만 COM과 같은 라이브러리의 경우 스스로 능동적으로 메모리 관리할 수 있다.

 

native code

native code는 unmanaged code와 동일한 의미로 사용된다. 옛날 버전의 컴파일러로 컴파일 되었거나 선택적으로 해당 컴퓨터에 적합하게 생성된 기계 언어를 말하며 실행시 CLR에 의한 서비스를 받을 수 없는 것을 말한다. 이러한 native code는 하나의 완전한 프로그램이거나 COM 컴포넌트 또는 managed code에서 호출된 DLL일 수도 있다. 

 

또 다른 의미로는 JIT 컴파일로부터 생성된 코드를 말한다. JIT은 managed code에만 사용되며 이는 managed code이지만 IL코드가 아니며 해당 컴퓨터에 적합한 기계언어이다. 따라서 native가 무조건 unmanaged는 아니다.

 

 

 

728x90
반응형

'Program Language > C#' 카테고리의 다른 글

C# 인스턴스 생성 키워드 : new  (0) 2023.03.30
Interoperability  (0) 2023.02.02
C# 외부 코드 사용 키워드 : extern  (1) 2023.01.31
C# 키워드 : delegate, event, action  (0) 2023.01.31
C# 상수 키워드 : const  (0) 2023.01.31

extern

'외부의'라는 뜻을 가지는 External의 약자로 외부에 선언된 함수 또는 객체임을 명시하는 역할을 한다.

이때 외부란 C# 코드 이외의 언어로 작성된 코드를 뜻하며 DLL이나 C/C++ 등으로 작성된 함수를 말한다.

이런 외부에서 작성된 코드 즉, C# 및. Net 프레임 워크에서 사용되지 않는 코드 다시 말해서 CLR에서 관리되지 않는 코드를 unmanaged code라고 하는데 이러한 unmanaged code를 불러오기 위해서는 interop 기술이 필요하다.

 

Interop 정리글

 

간략하게 Interop은 다른 언어나 플랫폼에서 작성된 코드를 호출하거나 받는 기술로 COM, P/Invoke, C++/CLI 등이 대표적으로 있는데 extern 키워드는 이러한 함수나 객체가 외부의 것임을 나타내는 역할을 한다.

 

일반적으로 C#에서 많이 사용하는 P/Invoke 방식은 DllImport arttribute와 함께 사용한다.

이 경우 메서드는 반드시 static으로 선언되어야 한다.

// Microsoft AVI File support library
[DllImport("avifil32.dll")] // Interop, P/Invoke
private static extern void AVIFileInit();

 

extern alias

.Net 어셈블리에서 다른 버전의 동일한 어셈블리의 참조가 필요한 경우에 사용할 수 있다.

일반적으로 .Net 프로젝트에서 어셈블리의 참조는 using이나 Imports를 사용하여 참조하는데 만약 서로 다른 버전의 어셈블리를 참조하는 경우에는 컴파일러가 어떤 버전을 사용해야 하는지 구분이 필요하기 때문에 이런 경우 extern alias 키워드를 사용한다.

 

동일한 어셈블리를 버전별로 참조하기 위해서는 각각의 어셈블리를 특정시킬 수 있도록 만드는 작업이 필요하다.

특정시키는 방법으로는 각 어셈블리의 별칭을 지정하는 방법과 이름을 변경하는 방법이 있다.

 

Alias

어셈블리의 별칭을 지정하는 방법은 대표적으로 두 가지 방법이 있다.

 

1. Visual Studio 

비주얼 스튜디오의 솔루션 탐색기에서 별칭을 수정할 수 있다.

Assembly > 우클릭 속성 > Aliases 수정

2. 프롬프트 창

프롬프트에서 어셈블리에 별칭을 부여할 수 있다.

/r:GridV1=grid.dll
/r:GridV2=grid20.dll

 

각각의 어셈블리에 외부에서 사용할 별칭 GridV1과 GridV2가 지정된다.

 

이렇게 별칭을 지정해 주면 extern 키워드를 사용하여 별칭을 참조하면 동일한 어셈블리를 구분해서 참조가 가능해진다.

extern alias GridV1;
extern alias GridV2;
// can using namespace
using Class1V1 = GridV1::Namespace.Class1;
using Class1V2 = GridV2::Namespace.Class1;

 

Name

별칭을 지정하지 않고 이름을 변경하는 방법으로도 참조가 가능하다.

비주얼 스튜디오에서 별칭을 수정했던 설정창을 다시 켜보면 상단에 이름 속성을 확인할 수 있다.

이름을 사용하기 위해서는 별칭이 지정되지 않아야 하므로 별칭을 지우고 이름을 수정한다.

 

그 후에 C#에서 Type.GetType 메서드를 사용하면 어셈블리에서 Type을 가져올 수 있는데 이때 어셈블리의 전체 이름을 사용하여 Type을 가져오면 해당 어셈블리를 특정해서 참조할 수 있게 된다.

Type myType = Type.GetType("MyNamespace.MyType, MyAssembly");

 

728x90
반응형

'Program Language > C#' 카테고리의 다른 글

Interoperability  (0) 2023.02.02
Managed, Unmanaged, Native Code  (0) 2023.02.02
C# 키워드 : delegate, event, action  (0) 2023.01.31
C# 상수 키워드 : const  (0) 2023.01.31
C# 비동기화 키워드 : async, await  (0) 2023.01.31

delegate

C/C++에는 함수를 가리키는 함수 포인터가 있다. 이를 사용하면 함수를 인수로 전달하거나 함수를 반환하는 등 다양한 방식으로 함수를 조작할 수 있는데 C#에서는 포인터를 직접적으로 사용할 수 없다. 그래서 C#에서 함수 포인터를 대신하는 개념으로 메서드를 참조하는 형식의 변수를 선언할 수 있도록 delegate를 사용한다.

 

delegate는 함수 포인터와 비슷한 개념으로 메서드에 대한 참조를 저장하여 메서드를 인수로 전달하거나 메서드를 반환하는 등의 작업을 수행할 수 있다. 메서드 자체를 하나의 변수로 다룰 수 있기 때문에 코드 자체를 참조하는 개념으로 볼 수 있다. 

선언

delegate void DelegateDefault();
delegate int DelegateReturn();
delegate int DelegateReturnParam(int x, int y);

static void Main(string[] args)
{
	DelegateDefault delegateDefault = new DelegateDefault(Print_1);
	DelegateReturn delegateReturn = new DelegateReturn(Print_2);
	DelegateReturnParam delegateReturnParam = new DelegateReturnParam(Print_3);

	delegateDefault();
	int result = delegateReturn();
	int result_2 = delegateReturnParam(1, 2);
}

static void Print_1()
{
	Console.WriteLine("Print_1");
}

static int Print_2()
{
	int num = 2;
	Console.WriteLine("Print_Out" + num);
	return num;
}

static int Print_3(int x, int y)
{
	int sum = x + y;
	Console.WriteLine("Print_Out" + sum);
	return sum;
}

선언된 델리게이트는 동일한 형식으로 선언된 메서드의 참조만 가능하다. 델게이트를 호출하면 참조중인 메서드를 호출하게 된다.

 

delegate chain

또한 델리게이트는 += 연산자를 통해서 동시에 여러 메서드를 참조할 수 있다.

delegate void DelegateChain();
DelegateChain delegateChain;
void Main(string[] args)
{
    delegateChain += Print_1;
    delegateChain += Print_2;
    delegateChain += Print_3;

	// ?.Invoke()를 사용하면 delegateChai이 null인 경우에 에러가 발생하는걸 방지할 수 있다.
    delegateChain?.Invoke();
    
    delegateChain -= Print_3;
    delegateChain -= Print_2;
    delegateChain -= Print_1;
}

static void Print_1() { }
static void Print_2() { }
static void Print_3() { }

참조중인 메서드 중에서 참조를 해제하려면 -= 연산자를 사용하면된다.

 

Anonymouse method call

델리게이트 체인을 활용해서 익명 메서드 호출도 가능하다.

void Main(string[] args)
{
    delegateChain += Print_1;
    delegateChain += Print_2;
    delegateChain += Print_3;
    delegateChain += delegate ()
    {
    	Console.WriteLine();
    };

    delegateChain?.Invoke();
    
	~
}

익명 메서드는 일회성으로 사용될 코드를 간단하게 작성할 때 주로 사용되며 특히 해당 코드가 특별한 용도를 제공하지 않거나 재사용될 필요가 없는 경우에 사용된다.

 

delegate는 대표적으로 이벤트 처리, 비동기 처리, 콜백에 사용된다.

 

Event

프로그래밍에서 이벤트는 어떤 특정한 조건이나 상황이 발생했을 때 이를 감지하고 처리하는 기능을 의미한다.

예를 들어 마우스 클릭, 키보드 입력 등이 이벤트이다. 이벤트 기반 프로그래밍(Event-driven programming)은 이러한 상황에서 반응하듯이 특정한 기능을 수행시키는 이벤트 기반으로 만드는 방식을 말하며 코드와의 상호작용으로 객체 간의 통신을 쉽게하고 객체의 라이프 사이클과 관련된 처리를 간편하게 할 수 있다.

 

Event Subscribe

이벤트 구독

일반적으로 이벤트를 다룰때 이벤트가 발생할때 동작시킬 기능에 해당하는 메서드의 할당과 참조는 외부에서 하더라도 이벤트의 발동은 델리게이트를 선언한 클래스 내에서 하는것이 권장된다.

 

외부에서 delegate에 메서드를 참조시키는걸 Subscribe, 참조한 메서드를 제거하는걸 Unsubscribe 라고 한다.

이렇게 외부에서 메서드를 참조하는 클래스를 Subscriber Class(구독자 클래스), 델리게이트가 선언된 클래스를 Publisher Class(게시자 클래스)라고 부른다.

 

public delegate MyDelegate();
// 게시자 클래스
public class MyPublisher
{
	// 델리게이트
	public MyDelegate MyEvent;
    public void OnEvent()
    {
    	MyEvent?.Invoke();
    }
}

// 구독자 클래스
public class MySubscriber
{
	private Publisher publisher = new Publisher();
	public void Subscirbe()
    {
    	// 구독
    	publisher.MyEvent += SomeMethod; 
    }
    
    public void UnSubscribe()
    {
    	// 해지
 		publicsher.MyEvent -= SomeMethod;
    }
    
    public void SomeMethod(){} 
}

 

event keyword

델리게이트를 이벤트 목적으로 사용할때 event 키워드를 사용한다.

event 키워드를 통해서 delegate가 선언되면 해당 델리게이트는 외부 클래스에서 실행이 불가능하게 된다.

따라서 외부 클래스는 이벤트를 구독과 해지만 가능하며 이벤트의 호출은 선언된 클래스 내부 오직 한곳에서만 일어나기 때문에 안전하게 사용할 수 있게 된다.

 

선언

class EventListner
{
    // 구독할 델리게이트 
    public delegate void EventHandler();
    // event를 통해서 MyEvent는 내부에서만 호출 가능해진다.
    public event EventHandler MyEvent;

    public void OnMyEvent()
    {
        // ? : EventName null check
        EventName?.Invoke();
    }
}

class SubscriberClass
{
	EventLister listner = new EventListenr();
    public void Subscribe()
    {
    	listner.MyEvent += SomeMethod
        // Cannot Call delegate
        lister.MyEvent?.Invoke();
    }
    
    public void SomeMethod(){}
}

 

event 제한자로 선언된 delegate를 외부 클래스에서 직접 호출을 시도하면 에러가 발생한다.

따라서 더 안전하게 이벤트를 구현할 수 있게 해주는 키워드라 할 수 있다.

 

Async

delegate의 비동기 작업은 일반적으로 멀티스레딩을 구현할 때 사용된다. 델리게이트를 통해서 메서드의 실행을 다른 스레드에게 위임하여 해당 스레드에서 비동기적으로 작업을 처리하고, 작업이 완료되면 해당 스레드가 호출한 스레드에게 결과를 알려준다. 이 작업 방식은 보통 결과를 필요로 하지 않는 작업을 빠르고 효율적으로 처리하기 위해서 사용한다.

 

보통 BeginInvoke()와 EndInvoke() 메서드를 활용해서 비동기를 구현한다.

* BeginInvoke : 해당 작업을 다른 스레드에서 비동기적으로 실행

* EndInvoke : 해당 작업이 완료되었는지 확인 후 결과를 반환

 

public delegate int AsyncMethodDelegate(int x, int y);

public class Calculator
{
    public int Add(int x, int y)
    {
        return x + y;
    }

	// Add 메서드를 비동기적으로 실행
    public void AddAsync(int x, int y, AsyncCallback callback)
    {
        AsyncMethodDelegate asyncMethod = new AsyncMethodDelegate(Add);
        // 작업이 종료되면 callback
        asyncMethod.BeginInvoke(x, y, callback, null);
    }
}

public class Program
{
    static void Main(string[] args)
    {
        Calculator calculator = new Calculator();
        AsyncCallback callback = new AsyncCallback(CalculateCompleted);

        calculator.AddAsync(1, 2, callback);
		
        Console.ReadLine();
    }
	
    // callback 호출 함수
    static void CalculateCompleted(IAsyncResult ar)
    {
        AsyncResult result = (AsyncResult)ar;
        AsyncMethodDelegate asyncMethod = (AsyncMethodDelegate)result.AsyncDelegate;

        int resultValue = asyncMethod.EndInvoke(ar);
        Console.WriteLine("Result: " + resultValue);
    }
}

 

Callback Function

콜백 함수는 다른 함수에서 인자로 전달되어 실행되는 함수를 말한다.

일반적으로 다른 함수의 작업이 완료된 후 호출된다.

 

만약 비동기적으로 실행되는 작업이 있을 때 해당 작업이 완료된 후 특정 동작을 실행시키고 싶다고 할때 여기서 콜백 함수를 사용할 수 있다. 비동기 작업을 수행하는 함수는 작업이 완료되면 콜백 함수를 호출하여 완료된 작업에 대한 처리를 수행하게 된다. 

 

콜백 함수는 함수 포인터(delegate)나 람다식 등의 방식으로 전달될 수 있으며 주로 이벤트 처리나 비동기 처리 등에서 사용된다.

 

EventHandler가 대표적인 콜백 함수라 볼 수 있다.

// 버튼 클릭 이벤트 처리를 위한 콜백 함수
private void Button_Click(object sender, EventArgs e)
{
    // 버튼이 클릭되었을 때 실행될 코드
}

// 이벤트 핸들러 등록을 위한 코드
button1.Click += new EventHandler(Button_Click);

버튼이 클릭되면 호출되는 Button_Click 메서드가 콜백 함수로 button1.Click 이벤트에 등록되어 클릭 이벤트가 발생하면 해당 콜백 함수가 실행되는 구조이다. 이렇게 delegate를 사용하여 콜백 함수를 등록함으로써 이벤트 처리와 같이 비동기적인 작업을 보다 효율적으로 처리할 수 있다.

 

Built-in delegate

C#의 내장형 delegate로 일반화된 형태의 델리게이트를 말한다.

매번 델리게이트를 선언하거나 만들 필요 없이 메서드를 매개변수로 전달하고 반환 값을 받을 수 있도록 하는것이 목적이다.

Action

Action은 C#의 내장 델리게이트 중 하나이다. 반환값이 없는 메서드를 참조할 수 있는 타입으로 매개변수를 받아서 반환값이 없는 메서드를 참조하는 기능을 한다.

Func

Func 또한 내장 델리게이트 중 하나로 입력 인자와 반환 값이 있는 일반적인 델리게이트 유형으로 반환 값을 가지는 메서드를 참조할 때 사용한다.

 

단순히 메서드를 전달 또는 메서드를 전달하고 반환 값을 받는 경우에는 Action이나 Func를 사용하는것이 코드를 간소화할 수 있다.

// Action을 사용하여 메서드를 매개변수로 전달하는 예시
public void PrintMessage(Action<string> messageAction)
{
    messageAction("Hello, world!");
}

// 메서드를 Action으로 전달하고 실행
PrintMessage(message => Console.WriteLine(message)); // "Hello, world!" 출력

// delegate를 쓴경우
public delegate void MessageDelegate(string message);	// 델리게이트 생성부터 필요
public void PrintMessage(MessageDelegate messageDelegate)
{
    messageDelegate("Hello, world!");
}

// 메서드를 delegate로 전달하고 실행
PrintMessage(message => Console.WriteLine(message)); // "Hello, world!" 출력


//==============================================================================



// Func을 사용하여 메서드를 매개변수로 전달하고 반환 값을 받는 예시
public int GetStringLength(Func<string, int> lengthFunc, string str)
{
    return lengthFunc(str);
}

// 메서드를 Func으로 전달하고 반환 값을 받습니다.
int strLength = GetStringLength(str => str.Length, "Hello"); // strLength에 5가 저장됩니다.


// delegate를 쓴경우
public delegate int LengthDelegate(string str);
public int GetStringLength(LengthDelegate lengthDelegate, string str)
{
    return lengthDelegate(str);
}

// 메서드를 delegate으로 전달하고 반환 값을 받습니다.
int strLength = GetStringLength(str => str.Length, "Hello"); // strLength에 5가 저장됩니다.

불필요한 delegate의 선언부를 줄일 수 있게 된다..

 

728x90
반응형

const

상수라는 뜻을가지는 Constant에서 따온 키워드이다.

상수란 프로그래밍에서 변하지 않는 값을 의미하는데 한번 값이 정해지면 프로그램이 실행되는 동안 그 값은  항상 일정하다. 컴파일 타임에 값이 결정되므로 런타임 시 메모리를 사용하지 않게 되어 상수를 사용하면 메모리 사용을 줄일 수 있다.

 

public class Program
{
	public static void Main(string[] args)
    {
    	const int A = 10;
        A = 10; // Compiler Error CS0131
    }
}

 

const 키워드로 선언된 변수는 상수로 취급되기 때문에 값을 재할당하면 컴파일 에러가 뜬다. 따라서 상수는 코드 흐름에서도 바뀔 필요가 없고 일정하게 사용될 값이 필요할 때 사용한다.

 

literal const

리터럴 상수 또한 상수와 마찬가지로 변하지 않는 값을 표현할 때 사 용는데 두 개념에는 약간의 차이가 있다.

상수는 const 예약어를 사용해서 변수형태로 선언되고 컴파일 시간에 값이 결정되어 프로그램 중에 값이 변경되지 않는 것이라면 리터럴 상수는 컴파일러가 코드에서 직접 사용할 수 있는 값을 의미한다. 예를 들어서 0, 1, 2, 3 등이 모두 정수형 리터럴 상수로 변수나 상수가 아니면서 프로그램 실행 중에도 변경할 수 없는 값이다.

 

// 상수 PI
const double PI = 3.14159265358979;
// 숫자 리터럴 상수
double radius = 3.0;
// 문자 리터럴 상수
char c = 'C';
// 문자열 리터럴 상수
string str = "Str";

리터럴 상수에는 숫자뿐만 아니라 문자 또한 포함된다.

즉 코드 상에서 선언되지 않았으면서 변수에 할당이 가능한 모든 값을 리터럴 상수라고 할 수 있다.

728x90
반응형

'Program Language > C#' 카테고리의 다른 글

C# 외부 코드 사용 키워드 : extern  (1) 2023.01.31
C# 키워드 : delegate, event, action  (0) 2023.01.31
C# 비동기화 키워드 : async, await  (0) 2023.01.31
C# 클래스 추상화 키워드 : abstract  (0) 2023.01.25
Main method  (0) 2023.01.19

async

메서드, 람다 표현식 또는 무명 메서드를 비동기로 특정할 수 있다.

메서드나 표현식에 async 제한자가 붙으면 비동기식 메서드라고 한다.

 

public async Task<T> ExampleMethodAsync()
{
	string contents = await NetworkManager.GetData(url);
}

 

async만 사용한다고 메서드가 비동기로 작동하는 것은 아니며 첫 번째 await 표현식을 만날 때까지 동기적으로 실행된다. 

await의 동작이 완료될 때까지 메서드는 대기하게 되고 메서드 호출자는 다음 동작을 진행하게 된다.

 

async 키워드로 선언된 비동기 메서드가 await 표현식이나 구문이 포함되어있지 않으면 컴파일러는 경고를 띄우게 된다.

비동기 메서드의 반환형은 Task <T>, Task(void) 형태로 정의되고 return T와 같이 해당 타입만 반환한다.

컴파일러는 return T을 자동으로 Task <T>로 변환한다. 특히 async 메서드는 이벤트핸들러를 위해 void의 반환을 허용하고 있다.

 

awiat은 일반적으로 Task 또는 Task <T> 객체와 함께 사용되는데 Task 이외에 awaitable 클래스도 함께 사용할 수 있다.

* awaitable : GetAwaiter() 메서드를 갖는 클래스

 

 

About sync and async

 

Example

    class Program
    {
        public static void Main(string[] args)
        {
            TaskTest(10);
            Console.WriteLine("============Call Main============");

            Console.ReadLine();
        }
        private static async void TaskTest(int count)
        {
            Console.WriteLine("============Call Task1============");

            for (int i = 0; i < count; i++)
            {
                var message = String.Format("Count {0}", i);
                Console.WriteLine(message);
                await Task.Delay(1000);
            }

            Console.WriteLine("============End Task1============");
        }
    }

Main 함수가 실행되면서 TaskTest를 호출한다.

그리고 곧이어 비동기적으로 Main 함수의 다음 코드가 실행되면서 TaskTest의 다음 동작이 진행된다.

 

 메인함수 또한 비동기로 정의하여 사용할 수 있다.

    class Program
    {
        public static async Task Main(string[] args)
        {
            await TaskTest(10);
        }
        private static async Task TaskTest(int count)
        {
            Console.WriteLine("============Call Task1============");

            for (int i = 0; i < count; i++)
            {
                var message = String.Format("Count {0}", i);
                Console.WriteLine(message);
                await Task.Delay(1000);
            }

            Console.WriteLine("============End Task1============");
        }
    }

메인함수는 await 기준으로 비동기적으로 작업이 처리된다.

 

요약

async 메서드 내부에서 await을 만날게 되면 다음과 같은 경우의 수 가 있다.

1. awaitable이 예외를 발생시킨다면 await은 throw exception 한다.

2. awaitable이 이미 종료됐다면 메서드는 일반 메서드와 같이 동기적으로 실행된다.

3. awaitable이 종료된 상태가 아니라면 종료될 때 await 다음 코드가 실행되도록 대기 큐에 등록하고 async 메서드의 호출자에게 Task를 반환한다.

 

microsoft document 비동기 프로그래밍 예제

https://learn.microsoft.com/ko-kr/dotnet/csharp/programming-guide/concepts/async/#final-version

 

C#의 비동기 프로그래밍

async, await 및 Task를 사용하여 비동기 프로그래밍을 지원하는 C# 언어에 대해 간략히 설명합니다.

learn.microsoft.com

 

 

728x90
반응형

'Program Language > C#' 카테고리의 다른 글

C# 키워드 : delegate, event, action  (0) 2023.01.31
C# 상수 키워드 : const  (0) 2023.01.31
C# 클래스 추상화 키워드 : abstract  (0) 2023.01.25
Main method  (0) 2023.01.19
테스트 환경  (0) 2023.01.17

+ Recent posts