답변:
좋아, 내가 이것을 더 명확하게 만들 수 있는지 보자.
첫째, Ash가 옳다. 문제는 값 유형 변수 가 어디에 할당 되는지에 관한 것이 아니다 . 그것은 다른 질문입니다. 그리고 그 대답은 단지 "스택"에 있지 않습니다. 그것보다 더 복잡합니다 (그리고 C # 2에 의해 훨씬 더 복잡해졌습니다). 나는 주제에 관한 기사를 가지고 있으며 요청이 있으면 확장 할 것이지만 연산자 만 다루겠습니다 .new
둘째,이 모든 것은 실제로 당신이 말하는 수준에 달려 있습니다. 컴파일러가 생성하는 IL과 관련하여 소스 코드로 컴파일러가 수행하는 작업을 살펴보고 있습니다. JIT 컴파일러가 많은 "논리적"할당을 최적화하는면에서 영리한 일을 할 가능성이 있습니다.
셋째, 나는 실제로 대답을 알지 못하고 부분적으로 너무 복잡하기 때문에 제네릭을 무시하고 있습니다.
마지막으로,이 모든 것은 현재 구현 된 것입니다. C # 스펙은이 중 많은 부분을 지정하지 않습니다. 실제로는 구현 세부 사항입니다. 관리 코드 개발자는 실제로 신경 쓰지 않아야한다고 생각하는 사람들이 있습니다. 나는 그렇게 멀리 갈지 확신하지 못하지만 실제로 모든 지역 변수가 힙에 살고있는 세계를 상상할 가치가 있습니다.
new
값 유형에 대한 연산자 에는 두 가지 상황이 있습니다. 매개 변수가없는 생성자 (예 :) new Guid()
또는 매개 변수가있는 생성자 (예 :)를 호출 할 수 있습니다 new Guid(someString)
. 이들은 상당히 다른 IL을 생성합니다. 이유를 이해하려면 C #과 CLI 사양을 비교해야합니다. C #에 따르면 모든 값 유형에는 매개 변수가없는 생성자가 있습니다. CLI 사양에 따르면 값 유형에 매개 변수가없는 생성자가 없습니다 . (반복으로 값 유형의 생성자를 가져 오십시오. 매개 변수가없는 것을 찾을 수 없습니다.)
C #에서는 "0으로 값 초기화"를 생성자로 취급하는 것이 좋습니다. 언어를 일관성있게 유지하기 때문 입니다. 항상 생성자를 호출하는 new(...)
것으로 생각할 수 있습니다 . 호출 할 실제 코드가없고 유형별 코드가 없기 때문에 CLI가 다르게 생각하는 것이 합리적입니다.
또한 초기화 후 값으로 수행 할 작업에 차이를 만듭니다. 사용 된 IL
Guid localVariable = new Guid(someString);
다음에 사용 된 IL과 다릅니다.
myInstanceOrStaticVariable = new Guid(someString);
또한 값이 중간 값으로 사용되는 경우 (예 : 메소드 호출에 대한 인수) 상황이 다시 약간 다릅니다. 이러한 모든 차이점을 보여주기 위해 간단한 테스트 프로그램이 있습니다. 그것은 정적 변수와 인스턴스 변수의 차이를 보여주지 않는다 : 일리노이 사이에 다른 것 stfld
하고 stsfld
있지만, 그게 다야.
using System;
public class Test
{
static Guid field;
static void Main() {}
static void MethodTakingGuid(Guid guid) {}
static void ParameterisedCtorAssignToField()
{
field = new Guid("");
}
static void ParameterisedCtorAssignToLocal()
{
Guid local = new Guid("");
// Force the value to be used
local.ToString();
}
static void ParameterisedCtorCallMethod()
{
MethodTakingGuid(new Guid(""));
}
static void ParameterlessCtorAssignToField()
{
field = new Guid();
}
static void ParameterlessCtorAssignToLocal()
{
Guid local = new Guid();
// Force the value to be used
local.ToString();
}
static void ParameterlessCtorCallMethod()
{
MethodTakingGuid(new Guid());
}
}
관련없는 비트 (예 : nops)를 제외한 클래스의 IL은 다음과 같습니다.
.class public auto ansi beforefieldinit Test extends [mscorlib]System.Object
{
// Removed Test's constructor, Main, and MethodTakingGuid.
.method private hidebysig static void ParameterisedCtorAssignToField() cil managed
{
.maxstack 8
L_0001: ldstr ""
L_0006: newobj instance void [mscorlib]System.Guid::.ctor(string)
L_000b: stsfld valuetype [mscorlib]System.Guid Test::field
L_0010: ret
}
.method private hidebysig static void ParameterisedCtorAssignToLocal() cil managed
{
.maxstack 2
.locals init ([0] valuetype [mscorlib]System.Guid guid)
L_0001: ldloca.s guid
L_0003: ldstr ""
L_0008: call instance void [mscorlib]System.Guid::.ctor(string)
// Removed ToString() call
L_001c: ret
}
.method private hidebysig static void ParameterisedCtorCallMethod() cil managed
{
.maxstack 8
L_0001: ldstr ""
L_0006: newobj instance void [mscorlib]System.Guid::.ctor(string)
L_000b: call void Test::MethodTakingGuid(valuetype [mscorlib]System.Guid)
L_0011: ret
}
.method private hidebysig static void ParameterlessCtorAssignToField() cil managed
{
.maxstack 8
L_0001: ldsflda valuetype [mscorlib]System.Guid Test::field
L_0006: initobj [mscorlib]System.Guid
L_000c: ret
}
.method private hidebysig static void ParameterlessCtorAssignToLocal() cil managed
{
.maxstack 1
.locals init ([0] valuetype [mscorlib]System.Guid guid)
L_0001: ldloca.s guid
L_0003: initobj [mscorlib]System.Guid
// Removed ToString() call
L_0017: ret
}
.method private hidebysig static void ParameterlessCtorCallMethod() cil managed
{
.maxstack 1
.locals init ([0] valuetype [mscorlib]System.Guid guid)
L_0001: ldloca.s guid
L_0003: initobj [mscorlib]System.Guid
L_0009: ldloc.0
L_000a: call void Test::MethodTakingGuid(valuetype [mscorlib]System.Guid)
L_0010: ret
}
.field private static valuetype [mscorlib]System.Guid field
}
보시다시피 생성자를 호출하는 데 사용되는 여러 가지 지침이 있습니다.
newobj
: 스택에 값을 할당하고 매개 변수화 된 생성자를 호출합니다. 필드에 할당하거나 메소드 인수로 사용하기 위해 중간 값에 사용됩니다.call instance
: 이미 할당 된 저장 위치를 사용합니다 (스택에 있든 없든). 이것은 로컬 변수에 할당하기 위해 위의 코드에서 사용됩니다. 동일한 지역 변수에 여러 번의 new
호출을 사용하여 값을 여러 번 할당 하면 이전 값의 상단에서 데이터를 초기화하기 때문에 매번 더 많은 스택 공간을 할당 하지 않습니다 .initobj
: 이미 할당 된 저장 위치를 사용하고 데이터를 지 웁니다. 이것은 로컬 변수에 할당되는 것을 포함하여 모든 매개 변수없는 생성자 호출에 사용됩니다. 메소드 호출의 경우 중간 로컬 변수가 효과적으로 도입되고 그 값이에 의해 삭제됩니다 initobj
.나는 이것이 주제가 얼마나 복잡한지를 보여 주면서 동시에 약간의 빛을 비추기를 바란다. 에서 몇 가지 개념 감각, 호출 할 때마다 new
스택에 할당 공간 -하지만 우리가 보았 듯이, 정말 심지어 IL 수준에서 무슨 일이 아니다. 하나의 특정 사례를 강조하고 싶습니다. 이 방법을 사용하십시오.
void HowManyStackAllocations()
{
Guid guid = new Guid();
// [...] Use guid
guid = new Guid(someBytes);
// [...] Use guid
guid = new Guid(someString);
// [...] Use guid
}
"논리적으로"는 4 개의 스택 할당 (변수마다 하나씩, 3 개의 new
호출 마다 하나씩)이 있지만 실제로는 (특정 코드에 대해) 스택은 한 번만 할당 된 다음 동일한 저장 위치가 재사용됩니다.
편집 : 분명히, 이것은 일부 경우에만 사실입니다 ... 특히 생성자가 예외를 throw guid
하면 값이 표시되지 않으므로 Guid
C # 컴파일러가 동일한 스택 슬롯을 재사용 할 수 있습니다. 자세한 내용과 적용 되지 않는 경우에 대한 가치 유형 구성에 대한 Eric Lippert의 블로그 게시물 을 참조하십시오 .
이 답변을 작성하면서 많은 것을 배웠습니다. 확실하지 않은 경우 설명을 요청하십시오!
List<Guid>
그 3을 가지고 추가 하면 어떻게됩니까 ? 그것은 3 개의 할당 일 것입니다 (동일한 IL)? 그러나 그들은 어딘가에 마법 유지하고
guid
어쨌든 표시되지 않으므로 값 이 절반으로 덮어 씌여 졌는지 여부는 중요하지 않습니다 .
구조체의 필드를 포함하는 메모리는 상황에 따라 스택 또는 힙에 할당 될 수 있습니다. struct-type 변수가 일부 익명 대리자 또는 반복자 클래스에 의해 캡처되지 않은 로컬 변수 또는 매개 변수 인 경우 스택에 할당됩니다. 변수가 일부 클래스의 일부인 경우 힙의 클래스 내에서 변수가 할당됩니다.
구조체가 힙에 할당 된 경우 실제로 메모리를 할당하는 데 새 연산자를 호출 할 필요는 없습니다. 유일한 목적은 생성자에있는 내용에 따라 필드 값을 설정하는 것입니다. 생성자가 호출되지 않으면 모든 필드는 기본값 (0 또는 null)을 갖습니다.
C #에서 모든 로컬 변수를 사용하기 전에 일부 값으로 설정해야한다는 점을 제외하고 스택에 할당 된 구조체와 마찬가지로 사용자 정의 생성자 또는 기본 생성자를 호출해야합니다 (매개 변수가없는 생성자는 항상 사용할 수 있습니다) 구조체).
간단히 말해서 new는 구조체의 잘못된 이름이며 new를 호출하면 단순히 생성자를 호출합니다. 구조체의 유일한 저장 위치는 정의 된 위치입니다.
멤버 변수 인 경우 정의 된대로 변수에 직접 저장되고, 로컬 변수 또는 매개 변수 인 경우 스택에 저장됩니다.
이것을 클래스와 대조하십시오. 클래스는 구조체가 완전히 저장된 위치에 대한 참조를 가지고 있지만 참조는 힙의 어딘가에 있습니다. (구성원의 로컬 / 매개 변수 내 멤버)
클래스 / 구조체가 실제로 구별되지 않는 C ++을 조금 보는 것이 도움이 될 수 있습니다. (언어에는 비슷한 이름이 있지만 기본 액세스 가능성 만 나타냅니다.) new를 호출하면 힙 위치에 대한 포인터가 표시되고 포인터가 아닌 참조가 있으면 스택에 직접 저장됩니다. 다른 개체 내에서 ala는 C #으로 구조체를 만듭니다.
모든 값 유형과 마찬가지로 구조체는 항상 선언 된 위치로 이동 합니다.
이 질문을 참조하십시오 여기 구조체를 사용하는 경우에 대한 자세한 내용은. 그리고이 질문에 여기 구조체에 대한 좀 더 많은 정보를 원하시면.
편집 : 나는 그들이 항상 스택에 들어가는 것에 대해 잘못 대답했다 . 이것은 잘못된 것 입니다.
아마도 여기에 뭔가 빠졌지 만 왜 할당에 관심이 있습니까?
값 형식은 value;)로 전달되므로 정의 된 위치와 다른 범위에서 변경할 수 없습니다. 값을 변경하려면 [ref] 키워드를 추가해야합니다.
참조 유형은 참조에 의해 전달되며 변경 될 수 있습니다.
물론 불변의 참조 유형 문자열이 가장 많이 사용됩니다.
배열 레이아웃 / 초기화 : 값 유형-> 메모리 없음 [name, zip] [name, zip] 참조 유형-> 메모리 없음-> null [ref] [ref]
class
또는 struct
선언은 실행시에 인스턴스 또는 객체를 생성하는 데 사용되는 청사진과 같다. class
또는 struct
Person 을 정의하면 Person이 유형의 이름입니다. Person 유형의 변수 p를 선언하고 초기화하면 p는 Person의 객체 또는 인스턴스라고합니다. 동일한 Person 유형의 여러 인스턴스를 작성할 수 있으며 각 인스턴스는 properties
및 에서 다른 값을 가질 수 있습니다 fields
.
A class
는 참조 유형입니다. 때의 개체class
가 생성 될 때 객체가 할당 된 변수는 해당 메모리에 대한 참조 만 보유합니다. 객체 참조가 새 변수에 할당되면 새 변수는 원래 객체를 나타냅니다. 하나의 변수를 통해 변경 한 내용은 다른 변수에 반영되므로 둘 다 동일한 데이터를 참조하기 때문입니다.
A struct
는 값 유형입니다. a struct
가 만들어 질 때 struct
지정된 변수 는 구조체의 실제 데이터를 유지합니다. struct
가 새 변수에 지정 되면 복사됩니다. 따라서 새 변수와 원래 변수에는 동일한 데이터의 두 개의 별도 사본이 포함됩니다. 한 사본을 변경해도 다른 사본에는 영향을 미치지 않습니다.
일반적으로 classes
보다 복잡한 동작 또는 class
개체를 만든 후 수정하려는 데이터를 모델링하는 데 사용됩니다 . Structs
은 주로 struct
생성 된 후 수정되지 않는 데이터를 포함하는 작은 데이터 구조에 가장 적합합니다 .
값 유형으로 간주되는 구조체는 스택에 할당되는 반면 객체는 힙에 할당되는 반면 객체 참조 (포인터)는 스택에 할당됩니다.
구조체는 스택에 할당됩니다. 다음은 유용한 설명입니다.
또한 .NET 내에서 인스턴스화되는 클래스는 힙 또는 .NET의 예약 된 메모리 공간에 메모리를 할당합니다. 반면에 구조체는 스택에 할당되어 인스턴스화 될 때 더 많은 효율성을 제공합니다. 또한 구조체 내에서 전달 매개 변수는 값으로 수행됩니다.