<23. 01. 09 (월)>
[ 닷넷 복습 ]
▶ JIT 컴파일 보다 AOT 컴파일 시간이 더 빠르다.
▶ CTS(공용 타입 시스템)
.NET(닷넷)에서는 여러 .NET 언어를 지원하기 위해 공용 타입 시스템을 지원한다.
.NET의 모든 형식은 값 타입(Value Type) 혹은 참조 타입(Reference Type)으로 구분되며 모든 타입은 기본 타입인 System.Object에서 파생된다.
▶ 박싱과 언박싱 ★정말 중요합니다.★
박싱은 값 타입을 object 타입 또는 값 타입에서 구현된 임이의 인터페이스 타입으로 변환하는 프로세스입니다.
언박싱은 박싱된 인터페이스에서 값 타입을 추출하는 프로세스입니다.
박싱과 언박싱이 일어나지 않도록 주의해야합니다.
프로그램의 복잡성이 점점 증가하다보면 결국 손도 댈 수 없는 프로그램이 됩니다. 프로그램의 복잡성을 줄이기 위해서는 프로그램을 여러 부분으로 나눠야합니다.
내가 알아야 하는 부분이 작으면 작을 수록 판단력이 빨라지고, 오류를 빠르게 수정을 할 수 있습니다.
단, 모듈화를 하면 전체를 파악하는데 시간이 오래걸립니다. 프로그램이 크면 클수록 모듈화가 상세하게 되어 있습니다. 의도 파악을 하는데 시간이 오래 걸릴 수 있습니다.
모듈은 쉽게 말하면 파츠라고 할 수 있습니다.
모듈화는 전체 프로그램을 여러 파트로 나누는 것을 말합니다.
모듈의 중요한 점은 무엇을 기준으로 나눌 것인지입니다.
모듈 하나하나는 논리적 또는 기능적으로 분리되어 독립적인 일을 수행 할 수 있어야합니다.
어떻게 모듈화를 했는가에 따라 프로그램의 유지보수성이 상이해집니다. 모듈화를 잘하는 방법은 결국 '경험'입니다.
또한 모듈화는 구현력이 받춰줘야 가능합니다. 구현이 안된다면 모듈화도 할 수 없습니다.
프로그래밍 언어에서는 모듈화를 위한 기능으로는 함수와 클래스가 있습니다. 클래스는 고차원의 모듈이라서 함수부터 먼저 배워야합니다.
Tip 객체 지향, 절차 지향 등 지향이 붙는다면 앞에있는 이름 친구로 만든 프로그램이라고 생각하면 편합니다.
객체 지향이면 객체로 만든 프로그램 이라는 의미입니다.
함수는 스택 메모리를 사용합니다.
스택은 함수가 사용해야하는 모든 것들을 가지고 있습니다.
스택은 정적 영역입니다. 정적 영역이란 프로그램 실행 전(컴파일 하기 전) 계산을 합니다.
얼만큼 메모리의 크키가 필요한지 계산을 한다는 의미입니다.
예를 들어 int형이 총 4개가 있다면 4byte 씩 4개니 총 16byte가 필요하겠죠? 스택은 이것을 컴파일 하기전 미리 계산하고 컴파일을 진행합니다.
함수는 일련의 과정에 이름을 붙인 것입니다.
함수는 코드 중복을 제거해 *프로그램의 모듈성을 높일 수 있습니다.
*프로그램을 프로시저로 나눠 작성하는 방식을 절차지향 프로그래밍이라고 합니다.
함수에 ()호출 연산자를 붙이면 함수를 호출 할 수 있으며, 호출이 되면 함수에 작성된 코드가 전부 실행됩니다.
함수의 실행이 끝나면 다시 호출했던 지점으로 돌아오며, 반환값을 주기도 합니다.

함수는 헤더(Header)와 바디(Body)로 나뉩니다.
<return - type> < identifier > <parmeter - list> < body>
예시를 들어보자면
1번 예시 : 입력된 값 중 큰 값 고르는 함수
int Max(int a, int b)
{
int result = (a > b) ? a : b;
return result;
}
1. int = <return - type> // 반환 타입
2. Max = <identifier> // 함수 이름
3. (int a, int b) = <parmeter - list> // 매개변수
4. <return - type> <identifier> <parmeter - list> 까지가 Header
5. {
int result = (a > b) ? a : b;
return result;
} = <body>
2번 예시


// int Add(int a, int b) 에서 a, b가 파리미터(인자)
// int C = Add(10, 20) 에서 10 ,20이 어규먼트(인수)
//위 사진에서 10과 20은 각각 a와 b에 대한 인자다. 라는 설명은 오류입니다.
// 10과 20은 a 와 b에 대한 인수입니다.
함수는 <parmeter - list> : 매개변수를 를 0개 이상 가질 수 있습니다.
즉, 아무것도 쓰지 않아도 된다는 것입니다. 우리가 흔히 사용하는 Main()에서 아무런 매개변수를 사용하지 않는 것 처럼 말입니다.
함수는 0개 이상의 매개변수를 가질 수 있으며, 매개변수는 함수의 바디에서 활용되는 변수입니다.
매개변수는 ()호출 연산자를 사용할 때 인수(Argument)에 의해 초기화 됩니다. 또한 return 구문을 사용해 호출자에게 결과값을 반환할 수 있습니다.
인수(Argument)는 매개변수의 초기값을 뜻합니다.
하지만 모든 함수가 반환값이 있지 않습니다. 그런 경우는 void를 사용하면 됩니다.
void를 사용하면 반환값을 사용하지 않습니다.
void = 빈 공간, 공허감
int Max(int a, int b)
{
int result = (a > b) ? a : b;
return result;
}
1. (int a, int b) : 입력
2. int result = (a > b) ? a : b; : 처리
3. return result; : 출력

함수는 뒤에 위치해도 동작합니다.

함수가 구문 하나일 경우에는 => 연산자로 식 본문 정의(Expression Body Definition)를 할 수 있습니다.

// 바꿔줍니다. void Print(string message) -> int로 바꿔줍니다.

변할 수 있는 인자라는 뜻입니다.
String. Format()을 보면 Format()은 인자를 다양하게 넘겨줄 수 있습니다.
어떻게 이게 가능할까? 이를 위한 기능은 가변 인자(Variadic Argument)라는 기능이 있습니다. 인자를 사용하려면 params라는 키워드를 사용하면 됩니다.
return 하면 함수가 종료됩니다. 만약 Main() 함수 바로 밑에 return 있다면 그 밑에 있는 명령어는 실행되지 않고 return이 있는 명령어만 실행되고 끝납니다.

// 1. 배열[]을 사용하면 .Length 사용이 가능합니다.
// 2. 위 함수의 식은 입력 받은 값 중 가장 큰 값을 고르는 방법입니다.
// 3. 가장 큰 값을 고르고 싶다면 큰 값을 저장할 변수를 선언하고, 큰 값을 변수에 저장하면 됩니다.

// 밑에 어떤 함수들이 있든 신경쓰지 않고 종료 됩니다.
어떻게 함수는 모든 작업이 끝났을 때, 다시 호출된 지점으로 돌아올 수 있을까?
함수는 프로세스 주소 공간 중 스택(stack)을 사용하는데, 스택에 함수를 동작하는데 필요한 모든 데이터를 저장합니다*. 이러한 데이터를 스택 프레임(stack Frame)이라고 합니다.
(이 부분이 핵심입니다.)
*그래서 함수 내부에서 사용되는 모든 메모리의 양은 컴파일 시간에 계산 되므로 정적 할당 영역이라고 합니다.
스택 프레임에 호출된 지점의 주소가 저장되어 있기에 함수가 끝나고 다시 호출 지점으로 돌아올 수 있는 것입니다.
스택 프레임은 디버그를 위해서라도 사용되는 데, 스택 프레임의 정보를 이용하면 함수가 어떤 순서로 호출★되고 있는지 확인할 수 있기 때문입니다.
(이 부분이 핵심입니다.)
★ 이를 호출 스택(Call stack)이라고 합니다.
스택의 메모리는 감소하는 방향으로 늘어납니다.
Process Control Block == PCB
문맥교환 공부할때 배웠던 PCB입니다.
BP - SP = stack의 크기를 알 수 있습니다.

https://kia-programming-38.slack.com/files/U04CNRNT6NA/F04HVCWTBBQ/stackoverflow.mp4
위 영상은 SP가 할당 영역을 초과하는 경우를 보여주는 영상입니다.
Stack Overflow
스택 프레임과 관련해 알아야 할 것이 바로 호출 규약(Calling Convention)입니다.
호출 규약은 함수가 어떻게 호출자로부터 인자를 받을 것이며, 결과값을 호출자에게 어떻게 돌려줄 것인지 규정해 놓은 것입니다.
즉, 스택 프레임에 어떤 데이터를 저장할 것인지 나타낸 것이라고 할 수 있습니다.
호출 규약은 프로세서 마다 다릅니다. ARM, x86-64 등 서로 상이합니다.
원래 32비트 아키텍처 시절만 해도 호출 규약이 굉장히 다양했으나, 64비트 아키텍처가 개발되면서부터는 거의 하나로 합쳐졌습니다. 이를 자세히 다루기에는 내용이 많이 어렵기 때문에 어느정도 실력이 쌓이고 다시 공부하는 것이 좋습니다.
우리가 호출 규약을 알아야 하는 이유는 성능과 깊은 연관이 있기 때문입니다.
▷범위
범위(Scope)는 객체가 유효한 영역을 말합니다.
어디서든 접근이 가능한 변수를 전역 변수(Global Variable)
블록(block) 범위 안에 있는 변수를 지역 변수(Local Variable)
지역 변수는 블록 범위에서만 유효합니다. 블록을 벗어나면 유효하지 않습니다.

// 여기서 전역 변수는 하나도 사용되지 않았습니다. 다 지역변수입니다.
▷ 수명
객체의 수명은 객체가 유효한 시간입니다.
지역 변수는 범위를 벗어나게 되면 바로 수명이 끝나게 됩니다.
스택 프레임에 의해서 알아서 정리가 될 것이기 때문입니다.
다만, 프로세스 주소 공간 중 힙이나, 데이터 영역에 저장되어 있는 객체는 프로그램이 끝날 때까지 수명이 유지 될 수 있습니다.
인자를 전달할 때는 2가지 방식이 있습니다.
1. 객체의 복사본을 전달하는 방식입니다. : Call by Value
2. 객체에 접근할 수 있는 주소를 전달하는 방식입니다. : Call by Reference
Tip. Call by Reference == Pass by Reference
프로그래머는 이 2가지 방식을 명확이 인지하고 있어야합니다. 왜냐하면 내가 쓴 코드가 원본에 영향을 주는지 알아야 하기 때문입니다.
복사본이 전달 된 경우 원본에 영향을 주지 않으나, 주소가 전달된 경우는 원본에 접근 할 수 있기 때문에 영향을 주게 됩니다.
기본적으로 인자 전달 방식은 복사본을 전달하는 것입니다.

// 참조 타입은 주소 값이 저장되는 타입으로써 x[0] = 10을 하면 arr[0] = 10으로 바뀐다.
// 단, x에 새로운 배열을 생성하면 지역변수 x가 가리키는 배열에 주소가 달라지므로 arr에 영향을 주지 않습니다.

// 위 그림에 설명처럼 ref를 사용하지 않으면 c, d만 바뀔뿐, a, b가 바뀌지 않습니다.
// 왜? 객체의 복사본을 전달 받았으니까! 당연히 a, b가 영향을 받지 않죠!


< 인자 전달 방식 중 주소를 전달하되 의도를 명확하게 하기 위해 in과 out을 사용할 수 있습니다.>
- in
- 읽기전용
- 코드의 안선정을 위해서 사용합니다.
- 인수로 보내는 변수는 초기화가 되어있어야 합니다.
- Pass by Reference이되 함수 내부에서 변경 불가능함을 명시적으로 나타낸다.
- 즉, 주소를 갖고 있지만 in을 사용하면 값을 변경할 수 없습니다. 읽기만 가능하니까!
- out
- 런타임 에러를 방지하고 컴파일 오류를 발생시켜줘서 프러그래머의 실수를 막아줄 수 있습니다.
- 다수의 데이터를 반환하는 함수를 만들고 싶을 때 사용합니다. => Parameter를 이용해야합니다.
- 강제성을 띕니다. 그래서 컴파일 오류를 발생 시켜주고 실수를 막아줍니다!
- Pass by Reference이되 함수에서 반환값 외에도 추가 데이터를 반환함을 명시적으로 나타낸다.
- 즉, out을 사용하면 함수 내부에서 무조건 내가 지정한 인자를 수정해줘야합니다.
// 무조건 age를 수정해줘야합니다. age를 수정해주지 않으면 오류가 발생한다.
// 수정해주지 않는다면?
▶인자 인수 구별법


// VariadicMax의 경우 파라미터(인자)는 numbers 입니다.
// VariadicMax의 경우 아규멘트(인수)는 0, 1, 2, 99, 3, 4, 6 입니다.