вот какая-то тестовая программа на С #:
using System;
struct Foo {
int x;
public Foo(int x) {
this.x = x;
}
public override string ToString() {
return x.ToString();
}
}
class Program {
static void PrintFoo(ref Foo foo) {
Console.WriteLine(foo);
}
static void Main(string[] args) {
Foo foo1 = new Foo(10);
Foo foo2 = new Foo(20);
Console.WriteLine(foo1);
PrintFoo(ref foo2);
}
}
и вот дизассемблированная скомпилированная версия метода Main:
.method private hidebysig static void Main (string[] args) cil managed {
// Method begins at RVA 0x2078
// Code size 42 (0x2a)
.maxstack 2
.entrypoint
.locals init (
[0] valuetype Foo foo1,
[1] valuetype Foo foo2
)
IL_0000: ldloca.s foo1
IL_0002: ldc.i4.s 10
IL_0004: call instance void Foo::.ctor(int32)
IL_0009: ldloca.s foo2
IL_000b: ldc.i4.s 20
IL_000d: newobj instance void Foo::.ctor(int32)
IL_0012: stobj Foo
IL_0017: ldloc.0
IL_0018: box Foo
IL_001d: call void [mscorlib]System.Console::WriteLine(object)
IL_0022: ldloca.s foo2
IL_0024: call void Program::PrintFoo(valuetype Foo&)
IL_0029: ret
} // end of method Program::Main
Я не понимаю, почему было создано newobj / stobj вместо простого вызова .ctor? Чтобы сделать его более загадочным, newobj + stobj оптимизирован jit-compiler в 32-битном режиме для одного вызова ctor, но не в 64-битном режиме ...
ОБНОВЛЕНИЕ:
Чтобы прояснить мое замешательство, ниже приведены мои ожидания.
выражение объявления типа значения, например
Foo foo = new Foo(10)
должен быть скомпилирован через
call instance void Foo::.ctor(int32)
выражение объявления типа значения, например
Foo foo = default(Foo)
должен быть скомпилирован через
initobj Foo
на мой взгляд, временная переменная в случае конструктивного выражения или экземпляр выражения по умолчанию следует рассматривать как целевую переменную, так как это не может следовать за каким-либо опасным поведением
try{
//foo invisible here
...
Foo foo = new Foo(10);
//we never get here, if something goes wrong
}catch(...){
//foo invisible here
}finally{
//foo invisible here
}
выражение присваивания вроде
foo = new Foo(10); // foo declared somewhere before
должен быть скомпилирован примерно так:
.locals init (
...
valuetype Foo __temp,
...
)
...
ldloca __temp
ldc.i4 10
call instance void Foo::.ctor(int32)
ldloc __temp
stloc foo
...
так я понимаю, что говорит спецификация С #:
7.6.10.1 Выражения создания объекта
...
Обработка во время выполнения выражения-создания-объекта формы new T (A), где T - это тип-класс или тип-структура, а A - необязательный список-аргументов, состоит из следующих шагов:
...
Если T является структурным типом:
Экземпляр типа T создается путем выделения временной локальной переменной. Поскольку от конструктора экземпляра типа структуры требуется однозначно присваивать значение каждому полю создаваемого экземпляра, инициализация временной переменной не требуется.
Конструктор экземпляра вызывается в соответствии с правилами вызова функций-членов (§7.5.4). Ссылка на вновь выделенный экземпляр автоматически передается в конструктор экземпляра, и к экземпляру можно получить доступ из этого конструктора как this.
я хочу сделать акцент на выделении временной локальной переменной. и, как я понимаю, инструкция newobj предполагает создание объекта в куче ...
Зависимость создания объекта от того, как он использовался, в данном случае меня расстраивает, поскольку foo1 и foo2 выглядят для меня одинаково.
newobj
выделяет память в куче для типов значений? Это не задокументировано. Я не понимаю, почему вы можете поверить в то, что прямо противоречит документации. - person Eric Lippert   schedule 05.03.2013