Virtaul
- virtaul의 경우는 컴파일 타임이 아니라 런타임에 함수 호출을 결정하는 키워드라고 알고 있음 virtaul 을 사용한 함수는 자식이 오버라이딩 함으로서 재정의 할 수 있고 이런 virtual 함수가 존재하는 클래스는 virtual table이 생성되어 이러한 vtable을 통해서 RTTI의 정보를 빼오기 때문에 런타임에 함수 호출을 결정할 수 있다. 동적 다형성이 가능한데, 아무래도 런타임에 확인하는 방식이다보니 런타임 오버헤드가 발생할 수 있다는 점이 단점이기 때문에 재정의를 할 필요가 없다면 사용하지 않는 것이 좋다고 알고 있다.
virtaul table의 경우는 가상함수가 존재하는 부모와 자식에게 클래스 별로 메모리에 존재하는 것으로 알고 있는데, 이런 객체의 경우는 vtable를 가리키는 포인터가 존재하기 때문에 이럴 활용해서 table을 호출할 수 있고, 만약 부모를 기준으로 자식 타입를 호출하게 되면 자식의 virtaul 함수가 호출되고 부모 타입으로 호출하게 되면 부모의 함수가 호출되는 방식이라고 알고 있다.
abstract class
- 추상 클래스는 혼자서 객체화 될 수 없다. 객체화가 가능한 클래스가 이 클래스를 통해서 다형성을 구현할 수 있다.
- Interface와 달리 멤버 변수를 사용할 수 있으며 상속이 가능한 형태이다.
+ 추가
- vtable은 클래스마다 하나씩 생성되는 고정된 배열로 함수의 메모리 주소를 담고 있다.
- vptr은 객체가 생성될 때 해당 객체의 메모리 첫 부분에 v-table을 가리키는 포인터가 숨겨져 있다.
RTTI는 가상 함수 테이블과 밀접한 관련이 있다. dynamic_cast나 typeid같은 연산자가 이 정보를 사용해서 런타임에 안전한 타입 변환이 가능한지 확인한다. 가상함수가 없는 클래스는 RTTI 정보가 없으므로 dynamic_cast를 사용할 수 없다.
- 상속이 있는 클래스에는 virtaul 소멸자를 사용하는 것이 좋다.
만약 부모 소멸자가 virtual이 아니라면 (부모* i = new 자식()) 형태에서 delete 시 자식의 소멸자가 호출되지 않아 메모리 누수가 발생할 수 있다.
Delegate 와 Event
- Delegate
1. 함수에 대한 포인터이다. 변수에 숫자를 담는 것처럼, 변수에 함수를 담아서 전달하거나 호출할 수 있게 해준다.
2. 클래스 외부에서 직접 호출 가능
3. = 연산자로 덮어쓰기 가능
4. 함수의 매개변수 전달, 콜백
- Event
1. 델리게이트의 래퍼
2. 외부 호출 불가
3. +=, -= 연산자만 사용 가능
4. 상태 변화 통지, Observer 패턴
Interface
- 내가 Interface를 활용한 시점은 Monster의 FSM을 구현할 당시 Enter, Execute, Exit를 집어넣고 상속하여 사용했던 것이 가장 기억에 남는다. 약간 데이터를 무조건 같은 매개변수와 이름으로 상속 받아야 한다는 점에서 순수 가상 함수와 비슷하다는 느낌을 받았다.
- 인터페이스란 멤버에 대한 선언만을 포함하며, 구현은 하지 않는 계약이다. 이를 구현하는 클래스나 구조체는 해당 멤버들을 반드시 구현해야 한다.
- 한 클래스가 여러 인터페이스를 동시에 구현할 수 있으며, 모든 멤버는 기본적으로 public으로 변경할 수 없다. 최신 C#에서는 디폴트 인터페이스 메서드가 도입되어 인터페이스도 구현체를 가질 수 있고 private 멤버도 선언 가능하다. 또한 구현을 강제하며, 반드시 구현해야 하는 기능을 정의할 때 사용된다.
인터페이스 -> 행위에 집
추상 클래스 -> 본질에 집중
- 인터페이스란 무엇이며, 언제 사용하는 것이 좋은가?
: 인터페이스란 멤버에 대한 선언만을 포함하며, 무조건 구현하도록 강제하는 계약입니다. 보통 한 클래스에 여러 인터페이스를 구현할 수 있으며, 모든 멤버는 기본적으로 public이며 접근제한자를 변경할 수 없다는 특징을 가지고 있습니다. 어떤 오브젝트에 대해 무조건 구현해야 하는 함수가 존재한다면 Interface를 통해서 강제하는 경우에 사용될 텐데, 저 같은 경우 프로젝트에서 FSM 패턴을 구현할 때 무조건 Enter, Excute, Exit를 구현해야 했기에 Interface를 통해 강제하여 함수를 무조건 정의할 수 있도록 했습니다.
- 다중으로 구현한다면 어떤 것이 좋은가?
: 다중으로 구현해본 적은 없지만, 만약 가능하다면 한 클래스가 다양한 기능을 수행할 수 있는 장점이 생길 것 같습니다. 예를 들어서 Moving을 하는데 필수적인 움직임과, Render를 하는데 있어 필수적인 함수를 함께 제공하는 오브젝트는 둘의 기능을 가질 수 있을 것입니다.
- virtaul과 Interface의 차이는 무엇인가?
: 둘다 확장성과 다형성을 구현하는데 사용하지만 사용 용도와 방식에 차이가 존재합니다. virtual의 경우는 이 함수를 필수적으로 구현하겠다고 계약할 때 사용하며, virtual은 기본적으로 구현된 함수를 제공하는데 더해 구현 용도는 선택 사항입니다. 그렇기 때문에 Interface는 필수적으로 구현해야 하는 마치 순수 가상 함수와 좀더 비슷한 느낌인 것 같고, virtaul은 공통 로직을 부모 클래스에 정의하고 공통적으로 사용한다는 점에서 차이가 존재하는 것 같습니다.
- 순수 가상 함수와 Interface의 차이는 무엇인가?
: 순수 가상 함수와 Interface는 구현을 강제한다는 점에서 유사한 역할을 합니다. 하지만 순수 가상 함수의 경우 같은 부모를 가지고 있는 상속 관계에서 사용됩니다. C++의 경우 Interface라는 개념이 존재하지 않기 때문에 이를 통해서 강제합니다. Interface의 경우는 상속 관계에 존재하지 않는 전혀 다른 오브젝트더라도 공통적인 계약을 할 수 있다는 차이가 있습니다. 순수 가상 함수는 단일 상속만 가능하며, Interface는 다중 구현이 가능하다는 차이점이 가장 큰 것 같습니다.,
+ 순수 가상함수는 추상 함수 외에 필드/변수, 일반 메소드 생성자 등을 포함할 수 있지만, Interface는 오직 선언만 가능
string/StringBuilder
- C#의 string은 불변 객체로, 문자열이 생성되면 그 값을 변경할 수 없다. 문자열을 수정하는 모든 작업은 새로운 string 객체를 생성해서 반환한다.
- 문자열에 문자를 추가하거나 수정하는 작업을 반복적으로 수행할 경우, 기존 문자열을 수정하는 것이 아니라 새로운 메모리 공간을 할당하게 된다. 이런 이유로 문자열 수정 작업이 많이 필요한 경우 성능이 저하될 수 있다.
- StringBuilder는 가변 객체로 문자열을 수정하는 과정에서 새로운 객체를 생성하지 않고 같은 메모리 공간을 사용하여 문자열을 변경한다. StringBuilder는 내부적으로 버퍼를 사용해 효율적으로 문자열을 추가하거나 변경할 수 있어 반복적인 문자열 조작 시 메모리 할당과 가비지 컬렉션의 부담을 준다.
- string : 불변 객체, 반복적으로 수정 시 새로운 메모리에 할당하여 메모리 사용량 증가 및 성능 저하, 문자열이 고정된 경우 유용
- StringBuilder : 가변 객체, 버퍼를 활용하여 반복적으로 수정하는 문자열에 유용, 대량의 문자열을 반복적으로 수정할 때 유용하다.
- string과 StringBuilder의 차이점은 무엇인가?
: string 의 경우 불변 객체로 값을 변경할 수 없습니다. 만약 수정을 하게 된다면 새로운 메모리에 string 객체를 생성하게 되는 것이기 때문에 반복적으로 수정한다면 메모리 사용량이 늘어나고 성능이 저하될 수 있습니다. 반대로, StringBuilder의 경우 가변 객체로 내부 버퍼를 사용하기 때문에 반복적으로 대량의 문자열을 수정할 때 유용하다고 알고 있습니다.
- StringBuilder 사용 시 유의할 점은 무엇인가요?
: StringBuilder는 문자열 조작 시에 적합하지만, 단순하고 적은 양의 문자열을 변경할 경우 string이 더 효율적입니다. 그 이유는 StringBuilder는 내부 버퍼를 관리하는데 추가적인 메모리 연산이 들기 때문에 만약 적은 양의 문자열을 변경한다면 차자리 string을 사용하는 것이 더 낫다고 알고 있습니다.
- String이 불변인 이유는 무엇인가요?
: 문자열 변경 시 새로운 인스턴스를 생성해서 메모리 단편화를 줄이며, 문자열 변경이 불가해 보안 상의 이유도 존재한다고 알고 있습니다. 그리고 여러 스레드에서 문자열을 안전히 공유할 수 있다는 이유인 것 같습니다.
"문자열 인턴풀" => 똑같은 문자열이 여러 개 생기지 않도록 메모리를 공유하기 위함인데, 불변이어야만 공유가 안전함
오버로딩과 오버라이딩의 차이점
- 뇌를 더듬더듬 살펴보면 오버로딩의 경우 반환 값 타입과 매개변수 개수 및 타입을 제외한 이름이 같은 함수를 의미하고, 오버라이딩은 상속 관련인데,,,, 상속,,, 부모에서 정의된 함수를 자식에서 다시 재정의하는 것... 중복 정의? virtaul함수가 오버라이딩인 것으로 알고 있음....
- 오버로딩 : 동일한 이름의 메서드만 정의하되, 각 메서드의 매개변수 목록을 다르게 설정하는 것을 의미한다. 매개변수의 타입, 개수, 순서가 다르면 서로 다른 메서드로 인식한다. 컴파일 시점에 어떤 메서드를 호출할 지 결정한다.
- 오버라이딩 : 상속 관계에서 자식 클래스가 부모 클래스 메서드를 재정의하는 것을 의미한다.같으 메서드를 구현하여 부모 클래스의 매서드를 변경하거나 확장할 수 있다. 이를 통해 다형성이 구현되며, 런타임 시점에서 실제 객체의 타입에 따라 호출되는 메서드가 달라진다.
- 반환 타입의 경우 오버로딩은 무관하지만, 오버라이딩은 부모와 같아야 함.
- 오버로딩과 오버라이딩의 차이점은 무엇인가요?
: 오버로딩의 경우 같은 메서드의 이름은 같되, 매개변수 목록을 달리하는(순서, 타입, 개수) 것을 의미합니다. 오버라이딩의 경우 상속 관계에서 부모의 함수를 그대로 자식이 확장하거나 변경하는 경우를 의미합니다. 그렇기 때문에 오버로딩은 컴파일 시점에 불릴 함수가 결정되지만, 오버라이딩은 객체의 타입에 따라서 런타임에 불릴 함수를 호출하는 것으로 알고 있습니다. 또 다른 차이점은 오버로딩은 반환 값이 달라도 상관 없지만, 오버라이딩은 부모와 같아야 합니다.
C#과 C++ 메모리 사용 방식
- 내가 알고 있는 차이점은 C#은 가비지 컬렉션이라는 것이 존재해서 메모리를 자동으로 관리하여 사용하지 않는 객체를 삭제시키지만 C++은 내가 직접 할당하고 삭제해야 하는 것으로 알고 있다.
- 또 다른 차이점이라하면 C#은 .Net 라이브러리를 사용하고 C++은 독립적인 언어라는 점? 그래서 C#이 좀더 편한데, 대신에 C++이 성능이 좋다고 함. 아마 가비지 컬렉션으로 인해서 주기적으로 오브젝트가 사용되지 않는 지 확인하지 않기 때문이겠지?
근데 알기로는 요새는 딱히 큰 차이는 없다고는 하는데 알아봐야 할 듯...
- C#은 .Net 환경에서 주로 애플리케이션 개발에 사용되며, 자동 메모리 관리 GC를 지원하여 안정적이고 생산적인 개발 환경을 제공한다. 순수한 객체 지향 언어이다.
- C++은 메모리 성능을 직접 관리할 수 있어 프로그래밍 성능이 중요한 애플리케이션에 적합하다. 또한 다양한 운영체제에서 네이티브 애플리케이션을 지원하는 플랫폼 독립적 언어이다. 객체 지향 뿐만 아니라, 절차적 프로그래밍, 함수형 프로그래밍을 지원하는 다중 패러다임 언어이다.
C++은 성능이 좋다 => 원하는 시점에 즉시 파괴가 가능하지만 GC가 언제 동작할지 몰라 예측하기 어렵다.
C++의 new/delete 동작 방식
- 기억나는 바로는 new의 경우에는 힙 메모리에 객체를 동적으로 생성하고 delete의 경우에는 객체를 제거... 어케 제거했더라...
operate delete랑 delete가 있는 걸로 알고 있는데...
- new의 경우 내부에 존재하는 operate new 연산자를 통해서 해당 넘겨준 객체의 크기만큼 메모리를 할당해주며 new를 통해서 생성자를 호출한다. 이 때문에 new는 객체 타입에 맞는 포인터를 전달한다.
- 추가로 malloc의 경우 넘겨준 크기만큼 메모리 할당만 하고 void*를 반환하여 원하는 타입으로 명시적 캐스팅이 필요하다.
- delete는 메모리 해제 전에 소멸자를 먼저 호출한다. (소멸자 호출 -> 메모리 해제)
C#에서 struct와 class의 차이
- struct : 값 타입으로 스택에 저장된다. 작고 수명이 짧은 데이터에 적합, 값 복사가 빈번히 일어날 수 있다. 상속 지원 x
- class : 참조 타입이며 힙에 저장된다. 기본적인 접근 제한자가 private이며 상속을 지원한다.
=> 접근 제한자를 명시하지 않으면 internal(기본 값) 혹은 멤버의 경우 private이다.
C++ string_view vs C# ReadOnlySpan
-
템플릿(C++)과 제네릭(C#)의 차이점
- 템플릿이라 하면 일단 C++에서 사용되는 한 함수를 여러 타입이 사용할 수 있도록 해서 코드 중복을 줄일 수 있고,,,
함수 템플릿이랑 ,클래스 템플릿이 있다는 거... 뭔지는 까먹음
- 둘 다 데이터 타입을 일반화하여 코드의 재사용을 높이는 기술. 내부 동작과 철학에 큰 차이가 존재한다.
- 템플릿
=> 실제 물건을 새로 만든다.
=> 컴파일러가 컴파일 타임에 타입을 결정한다. 코드 중복을 줄이기 위해 사용된다.
=> 타입별로 코드를 생성한다.
=> 메타 프로그래밍이 가능하다 (템플릿 특수화 같은)
- 제네릭
=> 하나의 상자에 다양한 물건을 안전하게 담을 수 있도록 하는 것
=> 컴파일 타임에 체크( 잘못된 타입이 들어오는 것을 방지 )하고 런타임에 타입 안정성을 제공한다. 다양한 타입에 대해 동일한 로직을 구현할 수 있다.
=> 런타임에 타입 정보를 유지해서 박싱 없이 최적화된 코드를 제공한다.
=> Object라는 코드를 공유하므로 메모리 사용이 효율적이다.
=> 메타 프로그래밍이 불가능하다 (단순 타입 안정성 보장 목적)
- 템플릿과 제네릭의 차이에 대해서 설명하세요
템플릿은 하나의 함수에 여러 타입들이 동일한 방식으로 같은 코드를 여러 번 치지 않고도 사용할 수 있도록 하는 것이다. 템플릿 특수화 같은 메타프로그래밍이 가능하며, 컴파일러가 컴파일 타임에 그 타입에 맞는 코드를 생성해준다. 장점으로 템플릿은 프로그래머가 코드를 중복적으로 사용하지 않아도 되지만, 단점으로는 아무래도 컴파일러가 타입을 사용한 만큼 코드를 생성하는 방식이다보니 코드 팽창이 일어날 수 있다.
제네릭의 경우 C#, Java에서 사용되는 개념인데 하나의 상자에 다양한 물건을 안전하게 넣는 느낌이다. 컴파일 타임에 이 타입이 안전한지 체크하고, 런타임에 제공하는 방식으로 사용한다. 실제 내부에서는 Object 등으로 변환되어 하나의 함수만 존재한다. 타입 소거를 통해 하나의 공통된 코드를 공유하므로 메모리 측면에서 효율적이다. 템플릿과 달리 메타 프로그래밍이 불가하다.
- 템플릿을 왜 헤더 파일에 작성해야 하는가?
템플릿 정의와 선언이 동시에 필요하기 때문에 헤더 파일에 작성해야 합니다. 컴파일 타임에 인스턴스화 되므로 정의가 필요합니다.
- 템플릿 특수화란 무엇인가?
템플릿 특수화란 일반 템플릿 처럼 모든 타입을 허용하는 것이 아니라, 특수한 타입에 대해서만 템플릿을 적용하는 것을 의미합니다. 특수화에는 모든 매개변수를 전부 구체적인 타입으로 제한하는 완전 특수화 방식과, 일부분만 제한하는 부분 특수화 방식이 존재합니다. 이를 통해서 해당 타입에 적합한 코드나 동작을 제공할 수 있습니다. 이를 통해 보다 최적화되고 안전한 동작을 보장하기 위해서 사용됩니다. 이를 통해 코드의 품질과 효율성을 높일 수 있다고 알고 있습니다.
- 함수 템플릿과 클래스 템플릿의 차이점은 무엇인가?
함수 템플릿은 여러 타입에 대해 동일한 동작 방식을 사용할 수 있도록 하는 역할을 합니다. 다양한 타입에 대해 중복 없이 정의해, 함수 호출 시에 컴파일러가 인자에 맞는 구체적인 함수를 인스턴스화 시줍니다. 함수 호출 시 컴파일러가 자동 추론하며, 부분 특수화는 지원하지 않습니다.
클래스 템플릿은 데이터를 저장하거나, 자료구조 및 클래스를 여러 타입에 맞게 재사용할 수 있는 클래스입니다. 특정 타입에 얽메이지 않고 작동하며 vector나 map 처럼 컨테이너 클래스를 구현할 때 사용됩니다. 템플릿 인자 추론을 지원해서 암시적 호출이 가능하고, 부분 특수화가 가능해 구체적인 구현이 가능합니다.
프랜드 클래스
프랜드 클래스를 알기론 A 클래스와 B클래스가 서로 참조했을 때 함께 참조되어 꼬이는 문제가 발생하는 걸로 알고 있다. 이때 friend 를 사용하면 이런 클래스가 있다고 알려만 주고 자세한 세부사항까지 알지 않아도 되는 것으로 알고 꼬이지 않게 하는 방식으로 알고 있는데... 이건 전방선언이랑 순환참조 문제구나
프랜드란 private, protected 로 보호된 멤버에 접근할 수 있도록 허용된 클래스 키워드로, 프랜드 클래스와 프랜드 함수로 일부 특정 부분을 프랜드 기능을 사용하거나 is-a도 has-a도 아닌 개별적인 객체에 대해서 조작할 수 있게끔한다.하지만 이런 프랜드 클래스의 경우 캡슐화 원칙을 희생하며 불가피하게 두 객체나 함수간의 결합도를 올리는 방식이기에 유의해서 사용해야 한다. 연산자 오버로딩이나, 내부 상태의 효율적인 테스트에서는 유용하게 사용할 수 있다.
프랜드 클래스란 무엇인가?
- 프랜드 클래스란 private, protected 멤버에도 접근이 가능하게 할 수 있도록 하는 클래스 키워드로, 프랜드 클래스를 사용하면 독립적인 객체에 대해서도 조작할 수 있게끔 한다는 특징이 있다. 이런 프랜드의 경우는 캡슐화의 원칙을 희생해서 불가피하게 두 객체의 결합도를 올리는 방식이기에 잘 사용하지 않는다. 하지만 디버깅을 하거나 연산자 오버로딩 등에 사용하면 유용하게 테스트가 가능하기 때문에 이런 경우에 유용하게 사용할 수 있다.
어째서 연산자 오버로딩이 프랜드 함수가 유용한가?
- 연산자 오버로딩은 이항 연산자의 경우 좌변 및 우변에 대한 동등한 변환을 허용해야 하는 상황이 많습니다. 원래는 좌변 피 연산자에만 암묵적 형 변환을 적용하지만 프랜드 함수를 구현하면 두 피연산자에 대해 동등한 암묵적 형 변환을 지원하며 클래스 내부 멤버에 접근이 가능하다는 장점이 존재합니다.
박싱/언박싱
- 박싱 : 값 타입을 참조 타입으로 변환하여 힙에 저장합니다 int i =123; object o = i;
- 언박싱 : 박싱된 객체를 다시 값 타입으로 변환한다. 힙 메모리에 있는 객체에서 원래의 값을 꺼내 스택으로 복사한다. 박싱된 타입과 꺼내려는 타입이 정확히 일치해야 한다.
성능 : 박싱이 일어날 때마다 힙 메모리에 새로운 객체가 생성되어 메모리 사용량이 늘어난다. 또한 GC에 부담이 있다. 힙에 생성된 객체는 나중에 가비지 컬렉터가 치워야 하기 때문이다. 단순 값 복사보다 더 느린 작업에 될 수 있다. => 비싼 작업
제네릭 : 제네릭이 방식/언박싱을 방지하기 위해서이다. 제네릭 이전에는 모든 데이터를 object로 받아 int를 넣을 때마다 박싱이 일어나 힙에 메모리 사용량이 늘어났지만, 제네릭 이후 List<int>를 만들면 처음부터 int로 처리하므로 박싱이 일어나지 않는다. => 값복사
"박싱을 피하기 위해 타입 전용의 기계어 코드를 따로 생성한다"
- ""
- "C#의 **interface와 abstract class**를 실무에서 어떤 기준으로 나누어 사용하시나요?"
인라인 함수란 무엇인가?
- 인라인 함수란 함수 호출에 대한 오버헤드를 줄이기 위해서 컴파일 타임에 함수 호출 부분에 직접 코드를 삽입하는 방식으로 처리하는 함수라고 알고 있음. 프로그래머가 inline 키워드를 붙여 사용하거나 컴파일러가 알아서 인라인화 시키는 두가지 방법이 있고, 이러한 인라인 함수는 함수 호출에 대한 오버헤드를 줄여줘서 짧으면서도 간결한 함수인데 자주 호출하는 함수에 대해서 유용하다고 알고 있음, 물론 재귀함수나 반복문이 존재하면 인라인 함수에는 적합하지 않고 보통 Getter Setter가 자주 인라인화되는 걸로 알고 있음
가비지 컬렉션이란?
내가 알고 있는 바로 가비지 컬렉션은 힙에 할당된 사용되지 않는 메모리들을 자동으로 해제해주는 메모리 관리 시스템이라고 알고 있다. 마크 앤 쉽? 이런 방식으로 사용하는 걸로 알고 있는데 일단 C# JAVA에서 사용되고,,, 자동으로 관리하고 있기 때문에 프로그래머가 메모리 관리를 해주지 않아도 된다는 점이 장점이지만 반대로 가비지 컬렉션이 실행될 때마다 부하가 발생할 수 있는 단점이 발생할 수 있고, 때문에 직접적으로 관리하는 C++보다 성능이 안 좋을 수 있다. GC는 0,1, 2세대로 나눠 관리하는데 이거에 대해서는 좀더 자세히 공부해봐야 할 것 같고,,
박싱, 언박싱이 이뤄지면 가비기 컬렉션이 좀더 많이 일하게 된다.
- 힙 메모리에 더이상 참조되지 않는 객체를 찾아내어 자동으로 메모리에서 해제하는 프로세스.
- 유니티에서 사용되는 작동 원리는 Mark & Sweep이다.
1. Mark표시로 Root 객체로부터 연결된 모든 객체를 추적하여 사용 중임을 표시한다.
2. Sweep : 표시되지 않은 객체는 메모리에서 삭제한다.
=> 최신 유니티 버전은 Incremental GC를 지원해서 한 번에 모든 가비지를 치우지 않고 여러 프레임에 걸쳐 조금씩 나눠 처리하여 게임이 멈추는 현상을 최소화 하는 것으로 최소화 한다.
가비지 컬렉션은 성능에 영향을 준다
1. 어떤 메모리가 필요 없는지 검사하는 동안 CPU 부하가 발생하여 게임의 메인 로직이 잠시 멈춘다
2. 메모리를 지우고 난 후 메모리 사이가 듬성 듬성해져, 큰 객체를 할당하려 할 때 공간은 춫ㅇ분해도 연속된 공간이 없어 메모리 부족 현상이 발생해 추가적인 힙 확장이 일어난다.
GC를 유발하는 상황들
1. string을 새로운 메모리 공간이 할당된다.
2. 업데이트 함수 안에 new를 할당하는 경우
3. Boxing이 일어나는 경우
4. LINQ를 사용하는 경우 편리하지만 내부적으로 많은 임시 객체를 생성한다.
최적화 방법
1. 문자열을 자주 변경하는 경우에는 stringBuilder를 사용한다.
2. 자주 생성 및 해제하는 객체의 경우 오브젝트 풀을 활용하여 재사용 한다.
3. 데이터 뭉치가 작고 빈번하게 사용된다면 힙이 아닌 스택에 저장되는 struct를 사용한다.
C#의 GC에서 세대별 관리(0, 1, 2세대)는 어떤 원리인가요?
0세대 : GC를 한 번도 겪지 않는 갓 생성된 객체
1세대 : GC를 1회 겪은 객체
2세대 : GC를 2회 이상 겪은 객체 (전체를 의미)
Awake() Start() 차이점
Awake() : 의 경우는 프리팹을 통해서 객체가 인스턴스화 된 직후에 한 번 호출되는 곳이라고 알고 있음
Start() : 의 경우 첫 프레임이 시작되기 전에 한 번 호출되는 것으로 알고 있음
GameObject 와 Component와 Prefab의 차이
GameObject : 아무 것도 없는 빈 그릇이라고 설명할 수 있을 것 같다. Transform은 존재하지만 Component가 없이 외형이나 물리를 가질 수 없다.
Component : GameObject에 붙일 수 있는 그릇이다. 여기에 Mesh Renderer나 Rigidbody등을 붙일 수 있따.
=> 이 둘은 Componsition 관계(합성 관계)라고 볼 수 있다. GameObject는 혼자 사용할 수 있지만 Component 없이 씬에 표현할 수 없고 Compoennt는 GameObject 없이 단독으로 사용할 수 없다.
Prefab : GameObject와 Componet의 집합인다 설정을 조율한 데이터 파일이라고 할 수 있을 것 같다. Prefab을 씬에 배치하면 이 Prefab을 토대로 한 Instance가 생성된다.
UGUI 를 최적화 하는 방법
UGUI의 가장 큰 특징은 레이아웃을 변경할 때마다 리빌드된다는 것이다.
리빌드 또한 일어나는 비용 요소 중 하나이기 때문에
1. 변하지 않는 것과 변하는 것을 구분해서 캔버스를 구분하게 되면 변하지 않는 것들까지 리빌드되는 것을 방지할 수 있다.
2. 마우스에 영향이 가지 않는 것은 RayCast Target이라는 옵션을 끔으로서 리빌드 시 발생하는 비용을 줄인다.
3. 코드를 작성할 때 SetActive()를 사용하면 리빌드가 발생하기 때문에 Alpha 값을 조정하거나, Canvas를 Disable 하는 방식을 사용한다.
리플렉션
- 실행 중인 프로그램이 자기 자신의 구조를 들여다보고 수정할 수 있는 기능, 프로그램이 자신의 정보를 스스로 파헤쳐보는 기술
- 일반적으로 코드는 컴파일 시점에 타입이 결정되지만, 리플렉션을 사용하면 런타임에 다음을 수행할 수 있다.
1. 객체의 정확한 타입 확인
2. 클래스 내부의 필드, 속성, 메서드 목록 조회
3. 이름만으로 특정 메서드를 호출하거나 필드 값 변경
4. prviate 선언된 접근 제한 멤버에도 접근 가능
언제 사용할까?
1. 스크립트를 짜면 유니티 에디터가 변수 이름을 읽어와 화면에 보여주는 원리가 리플렉션이다.
2. 직렬화 : 객체의 데이터를 JSON이나 XML로 변환할 때, 어떤 필드가 있는지 자동으로 찾아낼 때 사용한다.
3. 플러그인/모듈 시스템 : 컴파일 시점에는 알 수 없었던 외부 DLL 파일의 클래스를 로드하여 실행할 때 필수적
단점
리플렉션은 강력하지만 굉장히 비싼 기술이다.
컴파일 최적화를 거치지 않고 런타임에 일일이 이름을 대조하며 찾기에 성능 오버헤드가 일어난다.
메서드 이름을 문자열로 찾다가 오타가 나면 런타임에 앱에 튕기게 된다.