Z tego co wiem każdy program napisany w języku wysokopoziomowym by zadziałać najpierw jest kompilowany na asemblera następnie na kod maszynowy. Jest jakaś możliwość żeby zobaczyć swój program jako kod asemblerowy? (sorry jak coś pomieszałam i tak nie jest :P)
- Rejestracja:około 22 lata
- Ostatnio:około miesiąc
- Postów:5042
Co do rozgraniczenia na języki wysokiego i niskiego poziomu - to niejednoznaczne. Dla jednych językiem niskiego poziomu będzie asm, a np. C już będzie językiem wysokiego poziomu ;) Dla innych niekoniecznie.
Co do pytania. C# jest kompilowany do IL - Intermediate Language. Możesz się posłużyć takim narzędziem jak ILSpy do tego. Natomiast z tego co wiem, C# nie kompiluje się do typowego assemblera. C# pracuje na maszynie wirtualnej.

- Rejestracja:około 17 lat
- Ostatnio:około 2 godziny
- Lokalizacja:Wrocław



Program C# jest kompilowany w "locie" na instrukcje procesora po uruchomieniu programu .
Kompilacja w C# polega na przetworzeniu kodu źródłowego na język pośredni IL.
Typy podstawowe takie jak int , operacje dodawania, dzielenia itd są zdefiniowane w samym języku IL.
Implementacje niektórych metod dodaje też samo środowisko CLR . Wszystko po to żeby program działał jak najszybciej .
Do przeglądania kodu IL, metadanych , manifestu możesz użyć darmowego programu ildasm.exe, który powinien się znajdować w takim katalogu np.
"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.7.1 Tools\ildasm.exe"
Edytować kod IL najwygodniej jest w Visual Studio Code . A do ponownej kompilacji kodu IL możesz użyć programu Ilasm.exe . https://docs.microsoft.com/pl-pl/dotnet/framework/tools/ilasm-exe-il-assembler
Jest jeszcze takie coś: Kompilowanie aplikacji z architekturą .NET Native ale ja jeszcze tego nie próbowałem.
Specyfikację języka IL możesz pobrać ze strony : https://www.ecma-international.org/publications/standards/Ecma-335.htm
Warto znać język IL. Możesz wtedy ręcznie zoptymalizować kod .

- Rejestracja:około 17 lat
- Ostatnio:około 2 godziny
- Lokalizacja:Wrocław
Zimny Krawiec napisał(a):
Warto znać język IL. Możesz wtedy ręcznie zoptymalizować kod .
Zrobiłeś to kiedyś? Albo ktokolwiek kiedykolwiek?
Wiele razy . Na początku szło opornie ale potem język IL wydaje się bardzo prosty .
Jedna rzecz która jest trochę męcząca dla mnie , Standardowe biblioteki NET wyglądają trochę inaczej niż to co daje kompilator C# .
Wyglądają tak jakby ktoś je ręcznie pisał bez używania kompilatora.
Kompilator C# często dodaje dużo nadmiarowego kodu , który można pominąć .

- Rejestracja:około 17 lat
- Ostatnio:około 2 godziny
- Lokalizacja:Wrocław
A jakie praktyczne znaczenie ma objętość kodu? I o jakie rzeczy, które CLR może, a C# nie chodzi?
Dla mnie mierzalny efektem optymalizacji jest zoptymalizowanie czegoś. ;) Czyli szybkości działania albo zużycia pamięci w jakiś znaczący i zauważalny dla użytkowników sposób.
- Rejestracja:ponad 7 lat
- Ostatnio:4 miesiące
- Postów:1065
@somekind: No jak Ty nie rozumiesz. Przecież to proste: "musisz się przyzwyczaić że to działa na zasadzie stosu". STOSU!!! :)
ROTFL
Aaa, przypomniałem sobie, że to ja właściwie napisałem trochę kodu w IL ale to był inny IL, z IEC 61131-3. I tam faktycznie były rzeczy, których w innym języku się nie dało napisać, przynajmniej na starszych sterownikach Siemensa.


- Rejestracja:ponad 21 lat
- Ostatnio:minuta
W Visual Studio podczas debugowania można normalnie oglądać wykonywany kod binarny (i jego odpowiednik w asemblerze), również dla programów pisanych w C#.
Nie trzeba nic wiedzieć o IL ani nawet o jego istnieniu.
Z tym, że oglądanie wygenerowanego asemblera w wersji debug trochę mija się z celem, bo widzimy wersję niezoptymalizowaną kodu.
Jeśli chcemy zobaczyć co kompilator jest rzeczywiście w stanie z naszym kodem zrobić, trzeba debugować wersję release programu oraz wyłączyć w ustawieniach Visuala opcje "Suppress JIT optimization on module load" i "Enable Just My Code".
Jednak wtedy debugger potrafi zachowywać się dziwnie.
- v.png (68 KB) - ściągnięć: 240
Kod z optymalizacją i bez optymalizacji .
Gdybym optymalizował ręcznie kod to prawdopodobnie zrobiłbym to identycznie albo prawie identycznie.
using System;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
Console.Write("Podaj pierwszą liczbę : ");
int a = int.Parse(Console.ReadLine());
Console.Write("Podaj drugą liczbę : ");
int b = int.Parse(Console.ReadLine());
if (a > b) Console.WriteLine($"{a} > {b}");
else
Console.WriteLine($"{a} < {b}");
}
}
}
kod metody main bez optymalizacji kod IL:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 112 (0x70)
.maxstack 3
.locals init ([0] int32 a,
[1] int32 b,
[2] bool V_2)
IL_0000: nop
IL_0001: ldstr bytearray (50 00 6F 00 64 00 61 00 6A 00 20 00 70 00 69 00 // P.o.d.a.j. .p.i.
65 00 72 00 77 00 73 00 7A 00 05 01 20 00 6C 00 // e.r.w.s.z... .l.
69 00 63 00 7A 00 62 00 19 01 20 00 3A 00 20 00 ) // i.c.z.b... .:. .
IL_0006: call void [mscorlib]System.Console::Write(string)
IL_000b: nop
IL_000c: call string [mscorlib]System.Console::ReadLine()
IL_0011: call int32 [mscorlib]System.Int32::Parse(string)
IL_0016: stloc.0
IL_0017: ldstr bytearray (50 00 6F 00 64 00 61 00 6A 00 20 00 64 00 72 00 // P.o.d.a.j. .d.r.
75 00 67 00 05 01 20 00 6C 00 69 00 63 00 7A 00 // u.g... .l.i.c.z.
62 00 19 01 20 00 3A 00 20 00 ) // b... .:. .
IL_001c: call void [mscorlib]System.Console::Write(string)
IL_0021: nop
IL_0022: call string [mscorlib]System.Console::ReadLine()
IL_0027: call int32 [mscorlib]System.Int32::Parse(string)
IL_002c: stloc.1
IL_002d: ldloc.0
IL_002e: ldloc.1
IL_002f: cgt
IL_0031: stloc.2
IL_0032: ldloc.2
IL_0033: brfalse.s IL_0053
IL_0035: ldstr "{0} > {1}"
IL_003a: ldloc.0
IL_003b: box [mscorlib]System.Int32
IL_0040: ldloc.1
IL_0041: box [mscorlib]System.Int32
IL_0046: call string [mscorlib]System.String::Format(string,
object,
object)
IL_004b: call void [mscorlib]System.Console::WriteLine(string)
IL_0050: nop
IL_0051: br.s IL_006f
IL_0053: ldstr "{0} < {1}"
IL_0058: ldloc.0
IL_0059: box [mscorlib]System.Int32
IL_005e: ldloc.1
IL_005f: box [mscorlib]System.Int32
IL_0064: call string [mscorlib]System.String::Format(string,
object,
object)
IL_0069: call void [mscorlib]System.Console::WriteLine(string)
IL_006e: nop
IL_006f: ret
} // end of method Program::Main
kod IL po optymalizacji
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 102 (0x66)
.maxstack 3
.locals init ([0] int32 a,
[1] int32 b)
IL_0000: ldstr bytearray (50 00 6F 00 64 00 61 00 6A 00 20 00 70 00 69 00 // P.o.d.a.j. .p.i.
65 00 72 00 77 00 73 00 7A 00 05 01 20 00 6C 00 // e.r.w.s.z... .l.
69 00 63 00 7A 00 62 00 19 01 20 00 3A 00 20 00 ) // i.c.z.b... .:. .
IL_0005: call void [mscorlib]System.Console::Write(string)
IL_000a: call string [mscorlib]System.Console::ReadLine()
IL_000f: call int32 [mscorlib]System.Int32::Parse(string)
IL_0014: stloc.0
IL_0015: ldstr bytearray (50 00 6F 00 64 00 61 00 6A 00 20 00 64 00 72 00 // P.o.d.a.j. .d.r.
75 00 67 00 05 01 20 00 6C 00 69 00 63 00 7A 00 // u.g... .l.i.c.z.
62 00 19 01 20 00 3A 00 20 00 ) // b... .:. .
IL_001a: call void [mscorlib]System.Console::Write(string)
IL_001f: call string [mscorlib]System.Console::ReadLine()
IL_0024: call int32 [mscorlib]System.Int32::Parse(string)
IL_0029: stloc.1
IL_002a: ldloc.0
IL_002b: ldloc.1
IL_002c: ble.s IL_004a
IL_002e: ldstr "{0} > {1}"
IL_0033: ldloc.0
IL_0034: box [mscorlib]System.Int32
IL_0039: ldloc.1
IL_003a: box [mscorlib]System.Int32
IL_003f: call string [mscorlib]System.String::Format(string,
object,
object)
IL_0044: call void [mscorlib]System.Console::WriteLine(string)
IL_0049: ret
IL_004a: ldstr "{0} < {1}"
IL_004f: ldloc.0
IL_0050: box [mscorlib]System.Int32
IL_0055: ldloc.1
IL_0056: box [mscorlib]System.Int32
IL_005b: call string [mscorlib]System.String::Format(string,
object,
object)
IL_0060: call void [mscorlib]System.Console::WriteLine(string)
IL_0065: ret
} // end of method Program::Main

- Rejestracja:prawie 20 lat
- Ostatnio:około 23 godziny
Zimny Krawiec:
To czyste marnowanie czasu. Zysk dla użytkownika końcowego jest zerowy, albo ujemny, bo czas spędzony na oszczędzaniu kilku bajtów można przeznaczyć na poprawienie funkcjonalności czy wydajności (poprzez np polepszenie złożoności obliczeniowej, a nie samej stałej w złożoności).
Jeśli lubisz siedzieć w IL to jest fajne zadanie dla ciebie: https://github.com/oracle/graal/issues/349 :)




- Rejestracja:ponad 21 lat
- Ostatnio:minuta
Chory Lew napisał(a):
Oglądanie tego w ogóle mija się z celem . C# to nie jest niezarządzany kod C++ .
Z tego nic kompletnie nie wyczytasz . Co innego kod IL.
Jak to "nic nie wyczytasz"? To jest rzeczywiście wykonywany kod. Jak kogoś nie interesuje to niech nie ogląda, ale… moim zdaniem czytanie IL ma jeszcze mniejszy sens.

- Rejestracja:prawie 20 lat
- Ostatnio:około 23 godziny
Ja czasami czytam bajtkod Javowy, ale tylko jak chcę się upewnić jak coś tam działa pod spodem. Nie czytam bajtkodu po to, by ocenić jak dobrą (czy nie do końca dobrą) robotę zrobił kompilator.
Nie widzę sensu poprawiania bajtkodu skoro JIT w JVMie radzi sobie z optymalizacją (względnie) skomplikowanych abstrakcji na poziomie języka: dyskusja nad foreach w .NET i porównanie do Javy

- Rejestracja:ponad 21 lat
- Ostatnio:minuta
Zimny Krawiec napisał(a):
Bo ja odnośnie IL mogę ci wszystko wytłumaczyć co i jak ,
Tylko że… po co czytać IL, skoro masz ten sam kod w C#? :-)
Chyba tylko żeby zobaczyć na co przekłada się jakiś lukier składniowy, ot choćby ten foreach.
OK . Weźmy pod lupę instrukcję foreach na przykładzie typu List<T> , żeby lepiej zrozumieć jak to hula ;)
using System;
using System.Collections.Generic;
namespace ConsoleApp40
{
class Program
{
static void Main(string[] args)
{
List<int> lista = new List<int>(5);
lista.Add(1);
lista.Add(2);
lista.Add(3);
lista.Add(4);
lista.Add(5);
foreach (int item in lista)
{
Console.Write(item + ",");
}
}
}
}
Otrzymujemy kod IL:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 106 (0x6a)
.maxstack 2
.locals init ([0] class [mscorlib]System.Collections.Generic.List`1<int32> lista,
[1] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> V_1,
[2] int32 item)
IL_0000: ldc.i4.5
IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`1<int32>::.ctor(int32)
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldc.i4.1
IL_0009: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
IL_000e: ldloc.0
IL_000f: ldc.i4.2
IL_0010: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
IL_0015: ldloc.0
IL_0016: ldc.i4.3
IL_0017: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
IL_001c: ldloc.0
IL_001d: ldc.i4.4
IL_001e: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
IL_0023: ldloc.0
IL_0024: ldc.i4.5
IL_0025: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
IL_002a: ldloc.0
IL_002b: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<int32>::GetEnumerator()
IL_0030: stloc.1
.try
{
IL_0031: br.s IL_0050
IL_0033: ldloca.s V_1
IL_0035: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::get_Current()
IL_003a: stloc.2
IL_003b: ldloc.2
IL_003c: box [mscorlib]System.Int32
IL_0041: ldstr ","
IL_0046: call string [mscorlib]System.String::Concat(object,
object)
IL_004b: call void [mscorlib]System.Console::Write(string)
IL_0050: ldloca.s V_1
IL_0052: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()
IL_0057: brtrue.s IL_0033
IL_0059: leave.s IL_0069
} // end .try
finally
{
IL_005b: ldloca.s V_1
IL_005d: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>
IL_0063: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0068: endfinally
} // end handler
IL_0069: ret
} // end of method Program::Main
Czyli zamiast instrukcji forach możemy napisać tak : ( możemy też zrezygnować z jednej zmiennej )
using System;
using System.Collections.Generic;
namespace ConsoleApp40
{
class Program
{
static void Main(string[] args)
{
List<int> lista = new List<int>(5);
lista.Add(1);
lista.Add(2);
lista.Add(3);
lista.Add(4);
lista.Add(5);
using (List<int>.Enumerator item = lista.GetEnumerator())
{
while (item.MoveNext())
{
Console.Write(item.Current + ",");
}
}
}
}
}
albo krócej :. Myślę,że nikt się chyba nie zgorszy ;)
using System;
using System.Collections.Generic;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
using (List<int>.Enumerator item = new List<int> { 1, 2, 3, 4, 5 }.GetEnumerator())
{
while (item.MoveNext())
{
Console.Write(item.Current + ",");
}
}
}
}
}

@Mistrzowski Lew: czyli potwierdziłeś, że foreach
jest lukrem składniowym na GetEnumerator
;-)


- Rejestracja:ponad 7 lat
- Ostatnio:4 miesiące
- Postów:1065
Spoko. Hobbystycznie to sobie można sobie oglądać jak co działa ale w normalnej pracy to strata czasu. Ja jestem często po tej stronie co płaci i zwykle dość nerwowo reaguje na takie tematy bo dla mnie to strata czasu czyli pieniędzy.
Kiedyś znajomy pisał własny parser do XMl-a. A było to w czasach jak już był LINQ a On o tym nie wiedział. Sporo niepotrzebnych godzin i trzeba się było pożegnać.

