code

구조체에 "new"를 사용하면 힙이나 스택에 할당됩니까?

starcafe 2023. 5. 13. 10:28
반응형

구조체에 "new"를 사용하면 힙이나 스택에 할당됩니까?

으로 때new연산자, 메모리가 힙에 할당됩니다. ()로 때new연산자 메모리는 힙 또는 스택 중 어디에 할당됩니까?

좋아요, 제가 이걸 좀 더 명확히 할 수 있는지 봅시다.

첫째, 애쉬의 말이 맞습니다. 문제는 가치 유형 변수가 어디에 할당되는지에 대한 이 아닙니다.그것은 다른 질문입니다. 답은 단순히 "스택 위"가 아닙니다.이것은 그것보다 더 복잡합니다 (그리고 C# 2에 의해 훨씬 더 복잡해졌습니다).저는 그 주제에 대한 기사가 있고 요청이 있으면 그것에 대해 확장할 것입니다. 하지만 우리는 단지 그것만 다루자.new교환입니다.

두 번째로, 이 모든 것은 여러분이 말하는 수준에 달려 있습니다.저는 컴파일러가 소스 코드로 무엇을 하는지, 생성하는 IL 측면에서 보고 있습니다.JIT 컴파일러가 상당히 많은 "논리적" 할당을 최적화하는 측면에서 현명한 작업을 수행할 가능성이 높습니다.

세 번째로, 저는 제네릭을 무시하고 있습니다. 대부분은 제가 실제로 답을 모르기 때문입니다. 그리고 부분적으로는 그것이 일을 너무 복잡하게 만들 것이기 때문입니다.

마지막으로, 이 모든 것은 현재 구현된 것입니다.C# 사양에는 이에 대한 자세한 내용이 명시되어 있지 않습니다. 사실상 구현 세부 사항입니다.관리 코드 개발자들이 신경 쓰지 말아야 한다고 믿는 사람들이 있습니다.제가 그렇게까지 할 수 있을지는 모르겠지만, 사실 모든 지역 변수가 산더미처럼 쌓여 있는 세상을 상상할 가치가 있습니다. 여전히 사양과 일치할 것입니다.


두 가지 상황이 있습니다.new연산자 type: 없는: " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " 을 호출할 수 .new Guid()가 있는 생성자 ) 또 매 가 예 는 있 생 성 자 수 변 개 는 예(자▁)성생:▁(or▁a는▁orful▁constructe▁parameter있또gnew Guid(someString)). 이들은 상당히 다른 IL을 생성합니다.그 이유를 이해하려면 C#과 CLI 사양을 비교해야 합니다. C#에 따르면 모든 값 유형에는 매개 변수가 없는 생성자가 있습니다.CLI 규격에 따르면 어떤 값 유형에도 매개 변수 없는 생성자가 없습니다. (잠시 후 반사가 있는 값 유형의 생성자를 가져오면 매개 변수 없는 생성자를 찾을 수 없습니다.)

이 "값 일관되게 . - C# "0으로 값을 초기화"라고 . 언어를 일관되게 유지하기 때문입니다. 다음과 같이 생각할 수 있습니다.new(...)항상 건설자를 부르는 처럼.CLI는 호출할 실제 코드가 없고 유형별 코드도 없으므로 이를 다르게 생각하는 것이 타당합니다.

또한 값을 초기화한 후 값으로 수행할 작업도 달라집니다.사용된 IL

Guid localVariable = new Guid(someString);

다음에 사용되는 IL과 다릅니다.

myInstanceOrStaticVariable = new Guid(someString);

또한, 메소드 호출에 대한 인수와 같은 값을 중간 값으로 사용하면 상황이 다시 약간 다릅니다.이 모든 차이점을 보여주기 위해, 여기 짧은 테스트 프로그램이 있습니다.변수의 . 은 정 변 수 간 차 보 지 않 주 습 니 다 여 를 이 의 적 변 인 수 와 스 턴 스 니 다 ▁between 않 습 ▁variables : ▁differ ▁it ▁would ▁the ▁the ▁and ▁variables ▁instance▁doesn rence ▁il ▁between ▁static 정 IL은 다음 사이에서 다릅니다.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개입니다.new호출 - 실제로는 스택이 한 번만 할당된 다음 동일한 저장 위치가 재사용됩니다.

편집: 분명히 말씀드리자면, 이것은 어떤 경우에만 사실입니다... 특히, 의가의 값.guid다음과 같은 경우에는 보이지 않습니다.Guid생성자가 예외를 발생시키므로 C# 컴파일러가 동일한 스택 슬롯을 재사용할 수 있습니다.자세한 내용과 적용되지 않는 경우는 Eric Lippert의 가치 유형 구성에 대한 블로그 게시물을 참조하십시오.

이 답변을 쓰면서 많은 것을 배웠습니다. 명확하지 않은 답변이 있으면 설명을 요청하십시오!

구조체의 필드를 포함하는 메모리는 상황에 따라 스택 또는 힙에 할당될 수 있습니다.struct-type 변수가 일부 익명 대리자 또는 반복자 클래스에 의해 캡처되지 않는 로컬 변수 또는 매개 변수인 경우 스택에 할당됩니다.변수가 일부 클래스의 일부인 경우 힙의 클래스 내에서 할당됩니다.

구조체가 힙에 할당된 경우 메모리를 할당하기 위해 새 연산자를 호출할 필요가 없습니다.유일한 목적은 생성자에 있는 모든 항목에 따라 필드 값을 설정하는 것입니다.생성자가 호출되지 않으면 모든 필드가 기본값(0 또는 null)을 가져옵니다.

스택에 할당된 구조체의 경우 마찬가지로, C#을 사용하기 전에 모든 로컬 변수를 어떤 값으로 설정해야 하므로 사용자 지정 생성자 또는 기본 생성자를 호출해야 합니다(매개 변수를 사용하지 않는 생성자는 항상 구조체에 사용할 수 없음).

요약하자면, new는 구조체의 잘못된 이름으로, new는 단순히 생성자를 호출합니다.구조체의 유일한 저장 위치는 구조체가 정의된 위치입니다.

구성원 변수인 경우 정의된 대로 직접 저장되고 로컬 변수 또는 매개 변수인 경우 스택에 저장됩니다.

이를 전체 구조가 저장된 모든 곳에 참조가 있는 클래스와 비교하고 참조는 힙의 어딘가에 위치합니다. (구성원 내부, 스택의 로컬/매개 변수)

클래스/구조체 간의 실질적인 구별이 없는 C++을 조금 들여다보는 것이 도움이 될 수 있습니다. (언어에는 유사한 이름이 있지만, 그것들은 사물의 기본 접근성만을 지칭합니다.)new를 호출하면 힙 위치에 대한 포인터가 표시되고, non-pinter 참조가 있으면 스택에 직접 저장되거나 다른 개체 내에 저장되며, 이는 C#로 표시됩니다.

모든 값 유형과 마찬가지로 구조체는 항상 선언된 위치로 이동합니다.

구조체 사용 시기에 대한 자세한 내용은 여기에서 이 질문을 참조하십시오.그리고 구조에 대한 더 많은 정보를 위해 여기 이 질문이 있습니다.

편집: 제가 실수로 항상 스택에 들어간다고 대답했습니다.이것은 올바르지 않습니다.

제가 여기서 뭔가를 놓쳤을 수도 있는데 왜 우리는 할당에 신경을 쓰나요?

값 형식은 값 ;)에 의해 전달되므로 정의된 범위와 다른 범위에서 변환할 수 없습니다.값을 변경하려면 [ref] 키워드를 추가해야 합니다.

참조 유형은 참조를 통해 전달되며 변환할 수 있습니다.

물론 가장 인기 있는 것은 불변의 참조 유형 문자열입니다.

배열 레이아웃/초기화: 값 유형 -> 0 메모리 [name,zip][name,zip] 참조 유형 -> 0 메모리 -> null [ref][ref]

A class또는struct선언은 런타임에 인스턴스 또는 개체를 생성하는 데 사용되는 Blueprint와 같습니다. 당신이 경우한을 한다면.class또는struct사용자, 사용자는 유형의 이름입니다.Person 유형의 변수 p를 선언하고 초기화하면 p는 Person의 개체 또는 인스턴스라고 합니다. 개 수 , 각 수 .properties그리고.fields.

A class는 참조 유형입니다.의 가 될 때.class이 생성되면 개체가 할당된 변수는 해당 메모리에 대한 참조만 보유합니다.개체 참조가 새 변수에 할당되면 새 변수는 원래 개체를 참조합니다.두 변수 모두 동일한 데이터를 참조하기 때문에 한 변수를 통해 변경된 내용이 다른 변수에 반영됩니다.

A struct값 유형입니다.struct는 된성변수, 즉입니다.structAssigned는 구조체의 실제 데이터를 보유합니다. 때.struct새 변수에 할당되면 해당 변수가 복사됩니다.따라서 새 변수와 원래 변수에는 동일한 데이터의 두 개의 개별 복사본이 포함됩니다.한 복사본에 대한 변경사항은 다른 복사본에 영향을 주지 않습니다.

일적으로반,classes 복잡한 동작이나 됩니다.class개체가 생성됩니다. Structs주로 데이터를 포함하는 소규모 데이터 구조에 가장 적합합니다.struct생성됩니다.

더 많은...

값 유형으로 간주되는 구조체는 스택에 할당되는 반면 객체는 힙에 할당되는 반면 객체 참조(포인터)는 스택에 할당됩니다.

구조체가 스택에 할당됩니다.다음은 유용한 설명입니다.

구조체

또한 클래스는 내에서 인스턴스화됩니다.힙 또는 에 NET 메모리 할당.NET의 예약된 메모리 공간입니다.반면에 구조체는 스택에 할당되기 때문에 인스턴스화될 때 더 많은 효율성을 산출합니다.또한 구조체 내 매개변수 전달은 값에 의해 수행된다는 점에 유의해야 합니다.

언급URL : https://stackoverflow.com/questions/203695/does-using-new-on-a-struct-allocate-it-on-the-heap-or-stack

반응형