What happens when creating a variable? [closed]
Clash Royale CLAN TAG#URR8PPP
up vote
9
down vote
favorite
void Method1()
string str =
client.GetString("http://msdn.microsoft.com");
What exactly happens when the first line in Method1 is carried out?
I understand memory is set aside for the string variable str
, but does the right-hand side of the statement also get carried out at this stage? i.e. does it actually retrieve a value for the right-hand side?
c# variables
closed as too broad by Steve, D-Shih, techraf, Nick A, Servy Aug 13 at 15:49
Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer. Avoid asking multiple distinct questions at once. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.
 |Â
show 2 more comments
up vote
9
down vote
favorite
void Method1()
string str =
client.GetString("http://msdn.microsoft.com");
What exactly happens when the first line in Method1 is carried out?
I understand memory is set aside for the string variable str
, but does the right-hand side of the statement also get carried out at this stage? i.e. does it actually retrieve a value for the right-hand side?
c# variables
closed as too broad by Steve, D-Shih, techraf, Nick A, Servy Aug 13 at 15:49
Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer. Avoid asking multiple distinct questions at once. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.
2
@Alejandro: Right hand side of the assignment.
â Amadan
Aug 13 at 12:25
4
If the RHS didn't get carried out, what would be the point of having it?
â Scott Hunter
Aug 13 at 12:25
3
Yes. This is declaration (string str
) and initialisation (str = ...
) in one statement.
â Amadan
Aug 13 at 12:26
2
Code isnâÂÂt run line by line, especially when lines are cut in the middle of an actual code unit. Therefore itâÂÂs hard to say what you want to know exactly here and what to answer. There is only one line, which calls a method and stores the result in a variable. Compiler may throw the whole variable assignment out since itâÂÂs not used anywhere so there might not be any memory allocations.
â Sami Kuhmonen
Aug 13 at 12:27
2
"whether it was worthwhile storing a variable and then doing work on that variable later on" - quite possibly; depends entirely on context, though
â Marc Gravellâ¦
Aug 13 at 12:46
 |Â
show 2 more comments
up vote
9
down vote
favorite
up vote
9
down vote
favorite
void Method1()
string str =
client.GetString("http://msdn.microsoft.com");
What exactly happens when the first line in Method1 is carried out?
I understand memory is set aside for the string variable str
, but does the right-hand side of the statement also get carried out at this stage? i.e. does it actually retrieve a value for the right-hand side?
c# variables
void Method1()
string str =
client.GetString("http://msdn.microsoft.com");
What exactly happens when the first line in Method1 is carried out?
I understand memory is set aside for the string variable str
, but does the right-hand side of the statement also get carried out at this stage? i.e. does it actually retrieve a value for the right-hand side?
c# variables
c# variables
edited Aug 13 at 15:23
TylerH
15k104967
15k104967
asked Aug 13 at 12:23
jim bob
565
565
closed as too broad by Steve, D-Shih, techraf, Nick A, Servy Aug 13 at 15:49
Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer. Avoid asking multiple distinct questions at once. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.
closed as too broad by Steve, D-Shih, techraf, Nick A, Servy Aug 13 at 15:49
Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer. Avoid asking multiple distinct questions at once. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.
2
@Alejandro: Right hand side of the assignment.
â Amadan
Aug 13 at 12:25
4
If the RHS didn't get carried out, what would be the point of having it?
â Scott Hunter
Aug 13 at 12:25
3
Yes. This is declaration (string str
) and initialisation (str = ...
) in one statement.
â Amadan
Aug 13 at 12:26
2
Code isnâÂÂt run line by line, especially when lines are cut in the middle of an actual code unit. Therefore itâÂÂs hard to say what you want to know exactly here and what to answer. There is only one line, which calls a method and stores the result in a variable. Compiler may throw the whole variable assignment out since itâÂÂs not used anywhere so there might not be any memory allocations.
â Sami Kuhmonen
Aug 13 at 12:27
2
"whether it was worthwhile storing a variable and then doing work on that variable later on" - quite possibly; depends entirely on context, though
â Marc Gravellâ¦
Aug 13 at 12:46
 |Â
show 2 more comments
2
@Alejandro: Right hand side of the assignment.
â Amadan
Aug 13 at 12:25
4
If the RHS didn't get carried out, what would be the point of having it?
â Scott Hunter
Aug 13 at 12:25
3
Yes. This is declaration (string str
) and initialisation (str = ...
) in one statement.
â Amadan
Aug 13 at 12:26
2
Code isnâÂÂt run line by line, especially when lines are cut in the middle of an actual code unit. Therefore itâÂÂs hard to say what you want to know exactly here and what to answer. There is only one line, which calls a method and stores the result in a variable. Compiler may throw the whole variable assignment out since itâÂÂs not used anywhere so there might not be any memory allocations.
â Sami Kuhmonen
Aug 13 at 12:27
2
"whether it was worthwhile storing a variable and then doing work on that variable later on" - quite possibly; depends entirely on context, though
â Marc Gravellâ¦
Aug 13 at 12:46
2
2
@Alejandro: Right hand side of the assignment.
â Amadan
Aug 13 at 12:25
@Alejandro: Right hand side of the assignment.
â Amadan
Aug 13 at 12:25
4
4
If the RHS didn't get carried out, what would be the point of having it?
â Scott Hunter
Aug 13 at 12:25
If the RHS didn't get carried out, what would be the point of having it?
â Scott Hunter
Aug 13 at 12:25
3
3
Yes. This is declaration (
string str
) and initialisation (str = ...
) in one statement.â Amadan
Aug 13 at 12:26
Yes. This is declaration (
string str
) and initialisation (str = ...
) in one statement.â Amadan
Aug 13 at 12:26
2
2
Code isnâÂÂt run line by line, especially when lines are cut in the middle of an actual code unit. Therefore itâÂÂs hard to say what you want to know exactly here and what to answer. There is only one line, which calls a method and stores the result in a variable. Compiler may throw the whole variable assignment out since itâÂÂs not used anywhere so there might not be any memory allocations.
â Sami Kuhmonen
Aug 13 at 12:27
Code isnâÂÂt run line by line, especially when lines are cut in the middle of an actual code unit. Therefore itâÂÂs hard to say what you want to know exactly here and what to answer. There is only one line, which calls a method and stores the result in a variable. Compiler may throw the whole variable assignment out since itâÂÂs not used anywhere so there might not be any memory allocations.
â Sami Kuhmonen
Aug 13 at 12:27
2
2
"whether it was worthwhile storing a variable and then doing work on that variable later on" - quite possibly; depends entirely on context, though
â Marc Gravellâ¦
Aug 13 at 12:46
"whether it was worthwhile storing a variable and then doing work on that variable later on" - quite possibly; depends entirely on context, though
â Marc Gravellâ¦
Aug 13 at 12:46
 |Â
show 2 more comments
3 Answers
3
active
oldest
votes
up vote
13
down vote
This depends a lot on what you do next. There's a very good chance that the compiler will actually remove str
completely if you don't use it (unless you use it in the next step, or the things you do between now and then are "net zero" in terms of stack positions). It will still execute the call to client.GetString(...)
, of course; the question is what does it do with the result? There's various ways the compiler can interpret this:
- as a local:
stack space for the local is reserved as part of the stackframe entry; after the call to GetString
the compiler emits stloc
(or a variant)
- as an ambient stack value
no explicit stack space is reserved for the local; after the GetString()
it is simply left where it is for the next operation to consume (for example, this would be perfect if followed by a static call like Console.WriteLine(str);
); it might also be cloned (dup
) if needed multiple times
- popped
no explicit stack space is reserved for the local; after the GetString()
it is simply dropped (pop
)
- as a field
this would apply for iterator blocks and async methods; very complicated to explain
Ultimately, if you really want to know, you need to look at the real code, then look at the IL - ideally compiled in "release" mode.
You can see examples of some of these in this test code on sharplab.io
or copied here:
void Method1_Popped()
string str = client.GetString("http://msdn.microsoft.com");
void Method2_LeftOnStack()
string str = client.GetString("http://msdn.microsoft.com");
Console.WriteLine(str);
void Method3_Local()
string str = client.GetString("http://msdn.microsoft.com");
for(int i = 0;i < 3 ; i++) DoSomethingElse();
Console.WriteLine(str);
becomes:
.method private hidebysig
instance void Method1_Popped () cil managed
// Method begins at RVA 0x2050
// Code size 18 (0x12)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldfld class SomeClient Foo::client
IL_0006: ldstr "http://msdn.microsoft.com"
IL_000b: callvirt instance string SomeClient::GetString(string)
IL_0010: pop
IL_0011: ret
// end of method Foo::Method1_Popped
.method private hidebysig
instance void Method2_LeftOnStack () cil managed
// Method begins at RVA 0x2063
// Code size 22 (0x16)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldfld class SomeClient Foo::client
IL_0006: ldstr "http://msdn.microsoft.com"
IL_000b: callvirt instance string SomeClient::GetString(string)
IL_0010: call void [mscorlib]System.Console::WriteLine(string)
IL_0015: ret
// end of method Foo::Method2_LeftOnStack
.method private hidebysig
instance void Method3_Local () cil managed
// Method begins at RVA 0x207c
// Code size 42 (0x2a)
.maxstack 2
.locals init (
[0] string,
[1] int32
)
IL_0000: ldarg.0
IL_0001: ldfld class SomeClient Foo::client
IL_0006: ldstr "http://msdn.microsoft.com"
IL_000b: callvirt instance string SomeClient::GetString(string)
IL_0010: stloc.0
IL_0011: ldc.i4.0
IL_0012: stloc.1
// sequence point: hidden
IL_0013: br.s IL_001f
// loop start (head: IL_001f)
IL_0015: ldarg.0
IL_0016: call instance void Foo::DoSomethingElse()
IL_001b: ldloc.1
IL_001c: ldc.i4.1
IL_001d: add
IL_001e: stloc.1
IL_001f: ldloc.1
IL_0020: ldc.i4.3
IL_0021: blt.s IL_0015
// end loop
IL_0023: ldloc.0
IL_0024: call void [mscorlib]System.Console::WriteLine(string)
IL_0029: ret
// end of method Foo::Method3_Local
or as ASM:
Foo.Method1_Popped()
L0000: mov ecx, [ecx+0x4]
L0003: mov edx, [0xe42586c]
L0009: cmp [ecx], ecx
L000b: call dword [0x2ef71758]
L0011: ret
Foo.Method2_LeftOnStack()
L0000: push ebp
L0001: mov ebp, esp
L0003: mov ecx, [ecx+0x4]
L0006: mov edx, [0xe42586c]
L000c: cmp [ecx], ecx
L000e: call dword [0x2ef71758]
L0014: mov ecx, eax
L0016: call System.Console.WriteLine(System.String)
L001b: pop ebp
L001c: ret
Foo.Method3_Local()
L0000: push ebp
L0001: mov ebp, esp
L0003: mov ecx, [ecx+0x4]
L0006: mov edx, [0xe42586c]
L000c: cmp [ecx], ecx
L000e: call dword [0x2ef71758]
L0014: mov ecx, eax
L0016: call System.Console.WriteLine(System.String)
L001b: pop ebp
L001c: ret
sorry i have no idea what you said, but i will save the reply and hopefully one day i can come back to it and be able to understand it :) thanks marc, i've also saved the new edits
â jim bob
Aug 13 at 12:37
@jimbob I've added more context that may help you... or not :) and note: this is just in terms of IL; the JIT compiler can then do even more voodoo; there is also a "JIT Asm" tab on sharplab.io
â Marc Gravellâ¦
Aug 13 at 12:37
@jimbob ultimately, there's no way of answering this that doesn't involve talking about stackframes and IL...
â Marc Gravellâ¦
Aug 13 at 12:40
It seems you made a copy&paste mistake in the assembly listing:Foo.Method3_Local
is identical toFoo.Method2_LeftOnStack
.
â Marc Schütz
Aug 13 at 16:13
add a comment |Â
up vote
1
down vote
Memory allocation depends on how exactly the variable is used and where it's declared. In this case, being a local variable within a method*, memory is reserved as soon as the method is called (and not when execution reaches the place of its declaration), regardless of what could happen afterwards. So memory is set aside even if the client.GetString("http://msdn.microsoft.com")
isn't called at all (couldn't happen at all here, but in more complex code that's possible).
Note that you mention
when the first line in Method1 is carried out
This method has a single line, which consist in declaring a variable and assigning a value to it from calling another method. The fact that you've written it as two physical lines is irrelevant, as logically, your whole code consist of a single step. Again, variable "declaration" and allocation happens as soon as the method is called, and the rest of the line happens when execution reaches that point.
The execution of the line actually has two stages: the first, the GetString
method gets called. Second, its return value gets assigned to the local variable.
As Marc Gravell pointed out, things can become more complex. The compiler may decide to simply not create the variable at all, or arrange things differently, as long as that produces the same result (something known as compiler optimizations). The rest of this answer assumes that the compiler didn't optimized anything and created a binary that matches exactly the given code, but in release builds we can expect some differences.
*(and not captured by a lambda)
note - it is very possible (likely, even), that no stack space is actually reserved here; many local variables are elided completely by the compiler
â Marc Gravellâ¦
Aug 13 at 12:50
@MarcGravell Sure, in the case the compiler optimizes out the variable (probably in this case it does). For the simplicity sake, I'm assuming that the compiler compiles the code exactly as stands in the question, no optimizations involved. In real life, the actual binary might differ significantly from the code, as long as it produces the same result.
â Alejandro
Aug 13 at 13:06
1
One possible source of confusion: How can the compiler reserve memory at the start of the method when it doesn't even know how long the string is going to be? It might make sense to point out that space for the string content gets allocated somewhere within theGetString()
call, andMethod1
just stores a reference (to avoid the ugly word "pointer") to that space, with the amount of storage needed for the reference being always the same, independent of string length.
â Guntram Blohm
Aug 13 at 15:34
add a comment |Â
up vote
0
down vote
Yes, in this case, the assignment happens immediately. Later on you'll learn about funny things like lazy loading, async and delegates and then you'll see instances where variables aren't initialised immediately. But don't worry, by the time you learn about those things, you'll know enough that it won't be difficult to understand.
And congrats on learning the most beautiful programming language in the world.
add a comment |Â
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
13
down vote
This depends a lot on what you do next. There's a very good chance that the compiler will actually remove str
completely if you don't use it (unless you use it in the next step, or the things you do between now and then are "net zero" in terms of stack positions). It will still execute the call to client.GetString(...)
, of course; the question is what does it do with the result? There's various ways the compiler can interpret this:
- as a local:
stack space for the local is reserved as part of the stackframe entry; after the call to GetString
the compiler emits stloc
(or a variant)
- as an ambient stack value
no explicit stack space is reserved for the local; after the GetString()
it is simply left where it is for the next operation to consume (for example, this would be perfect if followed by a static call like Console.WriteLine(str);
); it might also be cloned (dup
) if needed multiple times
- popped
no explicit stack space is reserved for the local; after the GetString()
it is simply dropped (pop
)
- as a field
this would apply for iterator blocks and async methods; very complicated to explain
Ultimately, if you really want to know, you need to look at the real code, then look at the IL - ideally compiled in "release" mode.
You can see examples of some of these in this test code on sharplab.io
or copied here:
void Method1_Popped()
string str = client.GetString("http://msdn.microsoft.com");
void Method2_LeftOnStack()
string str = client.GetString("http://msdn.microsoft.com");
Console.WriteLine(str);
void Method3_Local()
string str = client.GetString("http://msdn.microsoft.com");
for(int i = 0;i < 3 ; i++) DoSomethingElse();
Console.WriteLine(str);
becomes:
.method private hidebysig
instance void Method1_Popped () cil managed
// Method begins at RVA 0x2050
// Code size 18 (0x12)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldfld class SomeClient Foo::client
IL_0006: ldstr "http://msdn.microsoft.com"
IL_000b: callvirt instance string SomeClient::GetString(string)
IL_0010: pop
IL_0011: ret
// end of method Foo::Method1_Popped
.method private hidebysig
instance void Method2_LeftOnStack () cil managed
// Method begins at RVA 0x2063
// Code size 22 (0x16)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldfld class SomeClient Foo::client
IL_0006: ldstr "http://msdn.microsoft.com"
IL_000b: callvirt instance string SomeClient::GetString(string)
IL_0010: call void [mscorlib]System.Console::WriteLine(string)
IL_0015: ret
// end of method Foo::Method2_LeftOnStack
.method private hidebysig
instance void Method3_Local () cil managed
// Method begins at RVA 0x207c
// Code size 42 (0x2a)
.maxstack 2
.locals init (
[0] string,
[1] int32
)
IL_0000: ldarg.0
IL_0001: ldfld class SomeClient Foo::client
IL_0006: ldstr "http://msdn.microsoft.com"
IL_000b: callvirt instance string SomeClient::GetString(string)
IL_0010: stloc.0
IL_0011: ldc.i4.0
IL_0012: stloc.1
// sequence point: hidden
IL_0013: br.s IL_001f
// loop start (head: IL_001f)
IL_0015: ldarg.0
IL_0016: call instance void Foo::DoSomethingElse()
IL_001b: ldloc.1
IL_001c: ldc.i4.1
IL_001d: add
IL_001e: stloc.1
IL_001f: ldloc.1
IL_0020: ldc.i4.3
IL_0021: blt.s IL_0015
// end loop
IL_0023: ldloc.0
IL_0024: call void [mscorlib]System.Console::WriteLine(string)
IL_0029: ret
// end of method Foo::Method3_Local
or as ASM:
Foo.Method1_Popped()
L0000: mov ecx, [ecx+0x4]
L0003: mov edx, [0xe42586c]
L0009: cmp [ecx], ecx
L000b: call dword [0x2ef71758]
L0011: ret
Foo.Method2_LeftOnStack()
L0000: push ebp
L0001: mov ebp, esp
L0003: mov ecx, [ecx+0x4]
L0006: mov edx, [0xe42586c]
L000c: cmp [ecx], ecx
L000e: call dword [0x2ef71758]
L0014: mov ecx, eax
L0016: call System.Console.WriteLine(System.String)
L001b: pop ebp
L001c: ret
Foo.Method3_Local()
L0000: push ebp
L0001: mov ebp, esp
L0003: mov ecx, [ecx+0x4]
L0006: mov edx, [0xe42586c]
L000c: cmp [ecx], ecx
L000e: call dword [0x2ef71758]
L0014: mov ecx, eax
L0016: call System.Console.WriteLine(System.String)
L001b: pop ebp
L001c: ret
sorry i have no idea what you said, but i will save the reply and hopefully one day i can come back to it and be able to understand it :) thanks marc, i've also saved the new edits
â jim bob
Aug 13 at 12:37
@jimbob I've added more context that may help you... or not :) and note: this is just in terms of IL; the JIT compiler can then do even more voodoo; there is also a "JIT Asm" tab on sharplab.io
â Marc Gravellâ¦
Aug 13 at 12:37
@jimbob ultimately, there's no way of answering this that doesn't involve talking about stackframes and IL...
â Marc Gravellâ¦
Aug 13 at 12:40
It seems you made a copy&paste mistake in the assembly listing:Foo.Method3_Local
is identical toFoo.Method2_LeftOnStack
.
â Marc Schütz
Aug 13 at 16:13
add a comment |Â
up vote
13
down vote
This depends a lot on what you do next. There's a very good chance that the compiler will actually remove str
completely if you don't use it (unless you use it in the next step, or the things you do between now and then are "net zero" in terms of stack positions). It will still execute the call to client.GetString(...)
, of course; the question is what does it do with the result? There's various ways the compiler can interpret this:
- as a local:
stack space for the local is reserved as part of the stackframe entry; after the call to GetString
the compiler emits stloc
(or a variant)
- as an ambient stack value
no explicit stack space is reserved for the local; after the GetString()
it is simply left where it is for the next operation to consume (for example, this would be perfect if followed by a static call like Console.WriteLine(str);
); it might also be cloned (dup
) if needed multiple times
- popped
no explicit stack space is reserved for the local; after the GetString()
it is simply dropped (pop
)
- as a field
this would apply for iterator blocks and async methods; very complicated to explain
Ultimately, if you really want to know, you need to look at the real code, then look at the IL - ideally compiled in "release" mode.
You can see examples of some of these in this test code on sharplab.io
or copied here:
void Method1_Popped()
string str = client.GetString("http://msdn.microsoft.com");
void Method2_LeftOnStack()
string str = client.GetString("http://msdn.microsoft.com");
Console.WriteLine(str);
void Method3_Local()
string str = client.GetString("http://msdn.microsoft.com");
for(int i = 0;i < 3 ; i++) DoSomethingElse();
Console.WriteLine(str);
becomes:
.method private hidebysig
instance void Method1_Popped () cil managed
// Method begins at RVA 0x2050
// Code size 18 (0x12)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldfld class SomeClient Foo::client
IL_0006: ldstr "http://msdn.microsoft.com"
IL_000b: callvirt instance string SomeClient::GetString(string)
IL_0010: pop
IL_0011: ret
// end of method Foo::Method1_Popped
.method private hidebysig
instance void Method2_LeftOnStack () cil managed
// Method begins at RVA 0x2063
// Code size 22 (0x16)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldfld class SomeClient Foo::client
IL_0006: ldstr "http://msdn.microsoft.com"
IL_000b: callvirt instance string SomeClient::GetString(string)
IL_0010: call void [mscorlib]System.Console::WriteLine(string)
IL_0015: ret
// end of method Foo::Method2_LeftOnStack
.method private hidebysig
instance void Method3_Local () cil managed
// Method begins at RVA 0x207c
// Code size 42 (0x2a)
.maxstack 2
.locals init (
[0] string,
[1] int32
)
IL_0000: ldarg.0
IL_0001: ldfld class SomeClient Foo::client
IL_0006: ldstr "http://msdn.microsoft.com"
IL_000b: callvirt instance string SomeClient::GetString(string)
IL_0010: stloc.0
IL_0011: ldc.i4.0
IL_0012: stloc.1
// sequence point: hidden
IL_0013: br.s IL_001f
// loop start (head: IL_001f)
IL_0015: ldarg.0
IL_0016: call instance void Foo::DoSomethingElse()
IL_001b: ldloc.1
IL_001c: ldc.i4.1
IL_001d: add
IL_001e: stloc.1
IL_001f: ldloc.1
IL_0020: ldc.i4.3
IL_0021: blt.s IL_0015
// end loop
IL_0023: ldloc.0
IL_0024: call void [mscorlib]System.Console::WriteLine(string)
IL_0029: ret
// end of method Foo::Method3_Local
or as ASM:
Foo.Method1_Popped()
L0000: mov ecx, [ecx+0x4]
L0003: mov edx, [0xe42586c]
L0009: cmp [ecx], ecx
L000b: call dword [0x2ef71758]
L0011: ret
Foo.Method2_LeftOnStack()
L0000: push ebp
L0001: mov ebp, esp
L0003: mov ecx, [ecx+0x4]
L0006: mov edx, [0xe42586c]
L000c: cmp [ecx], ecx
L000e: call dword [0x2ef71758]
L0014: mov ecx, eax
L0016: call System.Console.WriteLine(System.String)
L001b: pop ebp
L001c: ret
Foo.Method3_Local()
L0000: push ebp
L0001: mov ebp, esp
L0003: mov ecx, [ecx+0x4]
L0006: mov edx, [0xe42586c]
L000c: cmp [ecx], ecx
L000e: call dword [0x2ef71758]
L0014: mov ecx, eax
L0016: call System.Console.WriteLine(System.String)
L001b: pop ebp
L001c: ret
sorry i have no idea what you said, but i will save the reply and hopefully one day i can come back to it and be able to understand it :) thanks marc, i've also saved the new edits
â jim bob
Aug 13 at 12:37
@jimbob I've added more context that may help you... or not :) and note: this is just in terms of IL; the JIT compiler can then do even more voodoo; there is also a "JIT Asm" tab on sharplab.io
â Marc Gravellâ¦
Aug 13 at 12:37
@jimbob ultimately, there's no way of answering this that doesn't involve talking about stackframes and IL...
â Marc Gravellâ¦
Aug 13 at 12:40
It seems you made a copy&paste mistake in the assembly listing:Foo.Method3_Local
is identical toFoo.Method2_LeftOnStack
.
â Marc Schütz
Aug 13 at 16:13
add a comment |Â
up vote
13
down vote
up vote
13
down vote
This depends a lot on what you do next. There's a very good chance that the compiler will actually remove str
completely if you don't use it (unless you use it in the next step, or the things you do between now and then are "net zero" in terms of stack positions). It will still execute the call to client.GetString(...)
, of course; the question is what does it do with the result? There's various ways the compiler can interpret this:
- as a local:
stack space for the local is reserved as part of the stackframe entry; after the call to GetString
the compiler emits stloc
(or a variant)
- as an ambient stack value
no explicit stack space is reserved for the local; after the GetString()
it is simply left where it is for the next operation to consume (for example, this would be perfect if followed by a static call like Console.WriteLine(str);
); it might also be cloned (dup
) if needed multiple times
- popped
no explicit stack space is reserved for the local; after the GetString()
it is simply dropped (pop
)
- as a field
this would apply for iterator blocks and async methods; very complicated to explain
Ultimately, if you really want to know, you need to look at the real code, then look at the IL - ideally compiled in "release" mode.
You can see examples of some of these in this test code on sharplab.io
or copied here:
void Method1_Popped()
string str = client.GetString("http://msdn.microsoft.com");
void Method2_LeftOnStack()
string str = client.GetString("http://msdn.microsoft.com");
Console.WriteLine(str);
void Method3_Local()
string str = client.GetString("http://msdn.microsoft.com");
for(int i = 0;i < 3 ; i++) DoSomethingElse();
Console.WriteLine(str);
becomes:
.method private hidebysig
instance void Method1_Popped () cil managed
// Method begins at RVA 0x2050
// Code size 18 (0x12)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldfld class SomeClient Foo::client
IL_0006: ldstr "http://msdn.microsoft.com"
IL_000b: callvirt instance string SomeClient::GetString(string)
IL_0010: pop
IL_0011: ret
// end of method Foo::Method1_Popped
.method private hidebysig
instance void Method2_LeftOnStack () cil managed
// Method begins at RVA 0x2063
// Code size 22 (0x16)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldfld class SomeClient Foo::client
IL_0006: ldstr "http://msdn.microsoft.com"
IL_000b: callvirt instance string SomeClient::GetString(string)
IL_0010: call void [mscorlib]System.Console::WriteLine(string)
IL_0015: ret
// end of method Foo::Method2_LeftOnStack
.method private hidebysig
instance void Method3_Local () cil managed
// Method begins at RVA 0x207c
// Code size 42 (0x2a)
.maxstack 2
.locals init (
[0] string,
[1] int32
)
IL_0000: ldarg.0
IL_0001: ldfld class SomeClient Foo::client
IL_0006: ldstr "http://msdn.microsoft.com"
IL_000b: callvirt instance string SomeClient::GetString(string)
IL_0010: stloc.0
IL_0011: ldc.i4.0
IL_0012: stloc.1
// sequence point: hidden
IL_0013: br.s IL_001f
// loop start (head: IL_001f)
IL_0015: ldarg.0
IL_0016: call instance void Foo::DoSomethingElse()
IL_001b: ldloc.1
IL_001c: ldc.i4.1
IL_001d: add
IL_001e: stloc.1
IL_001f: ldloc.1
IL_0020: ldc.i4.3
IL_0021: blt.s IL_0015
// end loop
IL_0023: ldloc.0
IL_0024: call void [mscorlib]System.Console::WriteLine(string)
IL_0029: ret
// end of method Foo::Method3_Local
or as ASM:
Foo.Method1_Popped()
L0000: mov ecx, [ecx+0x4]
L0003: mov edx, [0xe42586c]
L0009: cmp [ecx], ecx
L000b: call dword [0x2ef71758]
L0011: ret
Foo.Method2_LeftOnStack()
L0000: push ebp
L0001: mov ebp, esp
L0003: mov ecx, [ecx+0x4]
L0006: mov edx, [0xe42586c]
L000c: cmp [ecx], ecx
L000e: call dword [0x2ef71758]
L0014: mov ecx, eax
L0016: call System.Console.WriteLine(System.String)
L001b: pop ebp
L001c: ret
Foo.Method3_Local()
L0000: push ebp
L0001: mov ebp, esp
L0003: mov ecx, [ecx+0x4]
L0006: mov edx, [0xe42586c]
L000c: cmp [ecx], ecx
L000e: call dword [0x2ef71758]
L0014: mov ecx, eax
L0016: call System.Console.WriteLine(System.String)
L001b: pop ebp
L001c: ret
This depends a lot on what you do next. There's a very good chance that the compiler will actually remove str
completely if you don't use it (unless you use it in the next step, or the things you do between now and then are "net zero" in terms of stack positions). It will still execute the call to client.GetString(...)
, of course; the question is what does it do with the result? There's various ways the compiler can interpret this:
- as a local:
stack space for the local is reserved as part of the stackframe entry; after the call to GetString
the compiler emits stloc
(or a variant)
- as an ambient stack value
no explicit stack space is reserved for the local; after the GetString()
it is simply left where it is for the next operation to consume (for example, this would be perfect if followed by a static call like Console.WriteLine(str);
); it might also be cloned (dup
) if needed multiple times
- popped
no explicit stack space is reserved for the local; after the GetString()
it is simply dropped (pop
)
- as a field
this would apply for iterator blocks and async methods; very complicated to explain
Ultimately, if you really want to know, you need to look at the real code, then look at the IL - ideally compiled in "release" mode.
You can see examples of some of these in this test code on sharplab.io
or copied here:
void Method1_Popped()
string str = client.GetString("http://msdn.microsoft.com");
void Method2_LeftOnStack()
string str = client.GetString("http://msdn.microsoft.com");
Console.WriteLine(str);
void Method3_Local()
string str = client.GetString("http://msdn.microsoft.com");
for(int i = 0;i < 3 ; i++) DoSomethingElse();
Console.WriteLine(str);
becomes:
.method private hidebysig
instance void Method1_Popped () cil managed
// Method begins at RVA 0x2050
// Code size 18 (0x12)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldfld class SomeClient Foo::client
IL_0006: ldstr "http://msdn.microsoft.com"
IL_000b: callvirt instance string SomeClient::GetString(string)
IL_0010: pop
IL_0011: ret
// end of method Foo::Method1_Popped
.method private hidebysig
instance void Method2_LeftOnStack () cil managed
// Method begins at RVA 0x2063
// Code size 22 (0x16)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldfld class SomeClient Foo::client
IL_0006: ldstr "http://msdn.microsoft.com"
IL_000b: callvirt instance string SomeClient::GetString(string)
IL_0010: call void [mscorlib]System.Console::WriteLine(string)
IL_0015: ret
// end of method Foo::Method2_LeftOnStack
.method private hidebysig
instance void Method3_Local () cil managed
// Method begins at RVA 0x207c
// Code size 42 (0x2a)
.maxstack 2
.locals init (
[0] string,
[1] int32
)
IL_0000: ldarg.0
IL_0001: ldfld class SomeClient Foo::client
IL_0006: ldstr "http://msdn.microsoft.com"
IL_000b: callvirt instance string SomeClient::GetString(string)
IL_0010: stloc.0
IL_0011: ldc.i4.0
IL_0012: stloc.1
// sequence point: hidden
IL_0013: br.s IL_001f
// loop start (head: IL_001f)
IL_0015: ldarg.0
IL_0016: call instance void Foo::DoSomethingElse()
IL_001b: ldloc.1
IL_001c: ldc.i4.1
IL_001d: add
IL_001e: stloc.1
IL_001f: ldloc.1
IL_0020: ldc.i4.3
IL_0021: blt.s IL_0015
// end loop
IL_0023: ldloc.0
IL_0024: call void [mscorlib]System.Console::WriteLine(string)
IL_0029: ret
// end of method Foo::Method3_Local
or as ASM:
Foo.Method1_Popped()
L0000: mov ecx, [ecx+0x4]
L0003: mov edx, [0xe42586c]
L0009: cmp [ecx], ecx
L000b: call dword [0x2ef71758]
L0011: ret
Foo.Method2_LeftOnStack()
L0000: push ebp
L0001: mov ebp, esp
L0003: mov ecx, [ecx+0x4]
L0006: mov edx, [0xe42586c]
L000c: cmp [ecx], ecx
L000e: call dword [0x2ef71758]
L0014: mov ecx, eax
L0016: call System.Console.WriteLine(System.String)
L001b: pop ebp
L001c: ret
Foo.Method3_Local()
L0000: push ebp
L0001: mov ebp, esp
L0003: mov ecx, [ecx+0x4]
L0006: mov edx, [0xe42586c]
L000c: cmp [ecx], ecx
L000e: call dword [0x2ef71758]
L0014: mov ecx, eax
L0016: call System.Console.WriteLine(System.String)
L001b: pop ebp
L001c: ret
edited Aug 13 at 12:45
answered Aug 13 at 12:32
Marc Gravellâ¦
758k18920892512
758k18920892512
sorry i have no idea what you said, but i will save the reply and hopefully one day i can come back to it and be able to understand it :) thanks marc, i've also saved the new edits
â jim bob
Aug 13 at 12:37
@jimbob I've added more context that may help you... or not :) and note: this is just in terms of IL; the JIT compiler can then do even more voodoo; there is also a "JIT Asm" tab on sharplab.io
â Marc Gravellâ¦
Aug 13 at 12:37
@jimbob ultimately, there's no way of answering this that doesn't involve talking about stackframes and IL...
â Marc Gravellâ¦
Aug 13 at 12:40
It seems you made a copy&paste mistake in the assembly listing:Foo.Method3_Local
is identical toFoo.Method2_LeftOnStack
.
â Marc Schütz
Aug 13 at 16:13
add a comment |Â
sorry i have no idea what you said, but i will save the reply and hopefully one day i can come back to it and be able to understand it :) thanks marc, i've also saved the new edits
â jim bob
Aug 13 at 12:37
@jimbob I've added more context that may help you... or not :) and note: this is just in terms of IL; the JIT compiler can then do even more voodoo; there is also a "JIT Asm" tab on sharplab.io
â Marc Gravellâ¦
Aug 13 at 12:37
@jimbob ultimately, there's no way of answering this that doesn't involve talking about stackframes and IL...
â Marc Gravellâ¦
Aug 13 at 12:40
It seems you made a copy&paste mistake in the assembly listing:Foo.Method3_Local
is identical toFoo.Method2_LeftOnStack
.
â Marc Schütz
Aug 13 at 16:13
sorry i have no idea what you said, but i will save the reply and hopefully one day i can come back to it and be able to understand it :) thanks marc, i've also saved the new edits
â jim bob
Aug 13 at 12:37
sorry i have no idea what you said, but i will save the reply and hopefully one day i can come back to it and be able to understand it :) thanks marc, i've also saved the new edits
â jim bob
Aug 13 at 12:37
@jimbob I've added more context that may help you... or not :) and note: this is just in terms of IL; the JIT compiler can then do even more voodoo; there is also a "JIT Asm" tab on sharplab.io
â Marc Gravellâ¦
Aug 13 at 12:37
@jimbob I've added more context that may help you... or not :) and note: this is just in terms of IL; the JIT compiler can then do even more voodoo; there is also a "JIT Asm" tab on sharplab.io
â Marc Gravellâ¦
Aug 13 at 12:37
@jimbob ultimately, there's no way of answering this that doesn't involve talking about stackframes and IL...
â Marc Gravellâ¦
Aug 13 at 12:40
@jimbob ultimately, there's no way of answering this that doesn't involve talking about stackframes and IL...
â Marc Gravellâ¦
Aug 13 at 12:40
It seems you made a copy&paste mistake in the assembly listing:
Foo.Method3_Local
is identical to Foo.Method2_LeftOnStack
.â Marc Schütz
Aug 13 at 16:13
It seems you made a copy&paste mistake in the assembly listing:
Foo.Method3_Local
is identical to Foo.Method2_LeftOnStack
.â Marc Schütz
Aug 13 at 16:13
add a comment |Â
up vote
1
down vote
Memory allocation depends on how exactly the variable is used and where it's declared. In this case, being a local variable within a method*, memory is reserved as soon as the method is called (and not when execution reaches the place of its declaration), regardless of what could happen afterwards. So memory is set aside even if the client.GetString("http://msdn.microsoft.com")
isn't called at all (couldn't happen at all here, but in more complex code that's possible).
Note that you mention
when the first line in Method1 is carried out
This method has a single line, which consist in declaring a variable and assigning a value to it from calling another method. The fact that you've written it as two physical lines is irrelevant, as logically, your whole code consist of a single step. Again, variable "declaration" and allocation happens as soon as the method is called, and the rest of the line happens when execution reaches that point.
The execution of the line actually has two stages: the first, the GetString
method gets called. Second, its return value gets assigned to the local variable.
As Marc Gravell pointed out, things can become more complex. The compiler may decide to simply not create the variable at all, or arrange things differently, as long as that produces the same result (something known as compiler optimizations). The rest of this answer assumes that the compiler didn't optimized anything and created a binary that matches exactly the given code, but in release builds we can expect some differences.
*(and not captured by a lambda)
note - it is very possible (likely, even), that no stack space is actually reserved here; many local variables are elided completely by the compiler
â Marc Gravellâ¦
Aug 13 at 12:50
@MarcGravell Sure, in the case the compiler optimizes out the variable (probably in this case it does). For the simplicity sake, I'm assuming that the compiler compiles the code exactly as stands in the question, no optimizations involved. In real life, the actual binary might differ significantly from the code, as long as it produces the same result.
â Alejandro
Aug 13 at 13:06
1
One possible source of confusion: How can the compiler reserve memory at the start of the method when it doesn't even know how long the string is going to be? It might make sense to point out that space for the string content gets allocated somewhere within theGetString()
call, andMethod1
just stores a reference (to avoid the ugly word "pointer") to that space, with the amount of storage needed for the reference being always the same, independent of string length.
â Guntram Blohm
Aug 13 at 15:34
add a comment |Â
up vote
1
down vote
Memory allocation depends on how exactly the variable is used and where it's declared. In this case, being a local variable within a method*, memory is reserved as soon as the method is called (and not when execution reaches the place of its declaration), regardless of what could happen afterwards. So memory is set aside even if the client.GetString("http://msdn.microsoft.com")
isn't called at all (couldn't happen at all here, but in more complex code that's possible).
Note that you mention
when the first line in Method1 is carried out
This method has a single line, which consist in declaring a variable and assigning a value to it from calling another method. The fact that you've written it as two physical lines is irrelevant, as logically, your whole code consist of a single step. Again, variable "declaration" and allocation happens as soon as the method is called, and the rest of the line happens when execution reaches that point.
The execution of the line actually has two stages: the first, the GetString
method gets called. Second, its return value gets assigned to the local variable.
As Marc Gravell pointed out, things can become more complex. The compiler may decide to simply not create the variable at all, or arrange things differently, as long as that produces the same result (something known as compiler optimizations). The rest of this answer assumes that the compiler didn't optimized anything and created a binary that matches exactly the given code, but in release builds we can expect some differences.
*(and not captured by a lambda)
note - it is very possible (likely, even), that no stack space is actually reserved here; many local variables are elided completely by the compiler
â Marc Gravellâ¦
Aug 13 at 12:50
@MarcGravell Sure, in the case the compiler optimizes out the variable (probably in this case it does). For the simplicity sake, I'm assuming that the compiler compiles the code exactly as stands in the question, no optimizations involved. In real life, the actual binary might differ significantly from the code, as long as it produces the same result.
â Alejandro
Aug 13 at 13:06
1
One possible source of confusion: How can the compiler reserve memory at the start of the method when it doesn't even know how long the string is going to be? It might make sense to point out that space for the string content gets allocated somewhere within theGetString()
call, andMethod1
just stores a reference (to avoid the ugly word "pointer") to that space, with the amount of storage needed for the reference being always the same, independent of string length.
â Guntram Blohm
Aug 13 at 15:34
add a comment |Â
up vote
1
down vote
up vote
1
down vote
Memory allocation depends on how exactly the variable is used and where it's declared. In this case, being a local variable within a method*, memory is reserved as soon as the method is called (and not when execution reaches the place of its declaration), regardless of what could happen afterwards. So memory is set aside even if the client.GetString("http://msdn.microsoft.com")
isn't called at all (couldn't happen at all here, but in more complex code that's possible).
Note that you mention
when the first line in Method1 is carried out
This method has a single line, which consist in declaring a variable and assigning a value to it from calling another method. The fact that you've written it as two physical lines is irrelevant, as logically, your whole code consist of a single step. Again, variable "declaration" and allocation happens as soon as the method is called, and the rest of the line happens when execution reaches that point.
The execution of the line actually has two stages: the first, the GetString
method gets called. Second, its return value gets assigned to the local variable.
As Marc Gravell pointed out, things can become more complex. The compiler may decide to simply not create the variable at all, or arrange things differently, as long as that produces the same result (something known as compiler optimizations). The rest of this answer assumes that the compiler didn't optimized anything and created a binary that matches exactly the given code, but in release builds we can expect some differences.
*(and not captured by a lambda)
Memory allocation depends on how exactly the variable is used and where it's declared. In this case, being a local variable within a method*, memory is reserved as soon as the method is called (and not when execution reaches the place of its declaration), regardless of what could happen afterwards. So memory is set aside even if the client.GetString("http://msdn.microsoft.com")
isn't called at all (couldn't happen at all here, but in more complex code that's possible).
Note that you mention
when the first line in Method1 is carried out
This method has a single line, which consist in declaring a variable and assigning a value to it from calling another method. The fact that you've written it as two physical lines is irrelevant, as logically, your whole code consist of a single step. Again, variable "declaration" and allocation happens as soon as the method is called, and the rest of the line happens when execution reaches that point.
The execution of the line actually has two stages: the first, the GetString
method gets called. Second, its return value gets assigned to the local variable.
As Marc Gravell pointed out, things can become more complex. The compiler may decide to simply not create the variable at all, or arrange things differently, as long as that produces the same result (something known as compiler optimizations). The rest of this answer assumes that the compiler didn't optimized anything and created a binary that matches exactly the given code, but in release builds we can expect some differences.
*(and not captured by a lambda)
edited Aug 13 at 13:13
answered Aug 13 at 12:39
Alejandro
3,95711936
3,95711936
note - it is very possible (likely, even), that no stack space is actually reserved here; many local variables are elided completely by the compiler
â Marc Gravellâ¦
Aug 13 at 12:50
@MarcGravell Sure, in the case the compiler optimizes out the variable (probably in this case it does). For the simplicity sake, I'm assuming that the compiler compiles the code exactly as stands in the question, no optimizations involved. In real life, the actual binary might differ significantly from the code, as long as it produces the same result.
â Alejandro
Aug 13 at 13:06
1
One possible source of confusion: How can the compiler reserve memory at the start of the method when it doesn't even know how long the string is going to be? It might make sense to point out that space for the string content gets allocated somewhere within theGetString()
call, andMethod1
just stores a reference (to avoid the ugly word "pointer") to that space, with the amount of storage needed for the reference being always the same, independent of string length.
â Guntram Blohm
Aug 13 at 15:34
add a comment |Â
note - it is very possible (likely, even), that no stack space is actually reserved here; many local variables are elided completely by the compiler
â Marc Gravellâ¦
Aug 13 at 12:50
@MarcGravell Sure, in the case the compiler optimizes out the variable (probably in this case it does). For the simplicity sake, I'm assuming that the compiler compiles the code exactly as stands in the question, no optimizations involved. In real life, the actual binary might differ significantly from the code, as long as it produces the same result.
â Alejandro
Aug 13 at 13:06
1
One possible source of confusion: How can the compiler reserve memory at the start of the method when it doesn't even know how long the string is going to be? It might make sense to point out that space for the string content gets allocated somewhere within theGetString()
call, andMethod1
just stores a reference (to avoid the ugly word "pointer") to that space, with the amount of storage needed for the reference being always the same, independent of string length.
â Guntram Blohm
Aug 13 at 15:34
note - it is very possible (likely, even), that no stack space is actually reserved here; many local variables are elided completely by the compiler
â Marc Gravellâ¦
Aug 13 at 12:50
note - it is very possible (likely, even), that no stack space is actually reserved here; many local variables are elided completely by the compiler
â Marc Gravellâ¦
Aug 13 at 12:50
@MarcGravell Sure, in the case the compiler optimizes out the variable (probably in this case it does). For the simplicity sake, I'm assuming that the compiler compiles the code exactly as stands in the question, no optimizations involved. In real life, the actual binary might differ significantly from the code, as long as it produces the same result.
â Alejandro
Aug 13 at 13:06
@MarcGravell Sure, in the case the compiler optimizes out the variable (probably in this case it does). For the simplicity sake, I'm assuming that the compiler compiles the code exactly as stands in the question, no optimizations involved. In real life, the actual binary might differ significantly from the code, as long as it produces the same result.
â Alejandro
Aug 13 at 13:06
1
1
One possible source of confusion: How can the compiler reserve memory at the start of the method when it doesn't even know how long the string is going to be? It might make sense to point out that space for the string content gets allocated somewhere within the
GetString()
call, and Method1
just stores a reference (to avoid the ugly word "pointer") to that space, with the amount of storage needed for the reference being always the same, independent of string length.â Guntram Blohm
Aug 13 at 15:34
One possible source of confusion: How can the compiler reserve memory at the start of the method when it doesn't even know how long the string is going to be? It might make sense to point out that space for the string content gets allocated somewhere within the
GetString()
call, and Method1
just stores a reference (to avoid the ugly word "pointer") to that space, with the amount of storage needed for the reference being always the same, independent of string length.â Guntram Blohm
Aug 13 at 15:34
add a comment |Â
up vote
0
down vote
Yes, in this case, the assignment happens immediately. Later on you'll learn about funny things like lazy loading, async and delegates and then you'll see instances where variables aren't initialised immediately. But don't worry, by the time you learn about those things, you'll know enough that it won't be difficult to understand.
And congrats on learning the most beautiful programming language in the world.
add a comment |Â
up vote
0
down vote
Yes, in this case, the assignment happens immediately. Later on you'll learn about funny things like lazy loading, async and delegates and then you'll see instances where variables aren't initialised immediately. But don't worry, by the time you learn about those things, you'll know enough that it won't be difficult to understand.
And congrats on learning the most beautiful programming language in the world.
add a comment |Â
up vote
0
down vote
up vote
0
down vote
Yes, in this case, the assignment happens immediately. Later on you'll learn about funny things like lazy loading, async and delegates and then you'll see instances where variables aren't initialised immediately. But don't worry, by the time you learn about those things, you'll know enough that it won't be difficult to understand.
And congrats on learning the most beautiful programming language in the world.
Yes, in this case, the assignment happens immediately. Later on you'll learn about funny things like lazy loading, async and delegates and then you'll see instances where variables aren't initialised immediately. But don't worry, by the time you learn about those things, you'll know enough that it won't be difficult to understand.
And congrats on learning the most beautiful programming language in the world.
answered Aug 13 at 12:46
Captain Kenpachi
4,12253049
4,12253049
add a comment |Â
add a comment |Â
2
@Alejandro: Right hand side of the assignment.
â Amadan
Aug 13 at 12:25
4
If the RHS didn't get carried out, what would be the point of having it?
â Scott Hunter
Aug 13 at 12:25
3
Yes. This is declaration (
string str
) and initialisation (str = ...
) in one statement.â Amadan
Aug 13 at 12:26
2
Code isnâÂÂt run line by line, especially when lines are cut in the middle of an actual code unit. Therefore itâÂÂs hard to say what you want to know exactly here and what to answer. There is only one line, which calls a method and stores the result in a variable. Compiler may throw the whole variable assignment out since itâÂÂs not used anywhere so there might not be any memory allocations.
â Sami Kuhmonen
Aug 13 at 12:27
2
"whether it was worthwhile storing a variable and then doing work on that variable later on" - quite possibly; depends entirely on context, though
â Marc Gravellâ¦
Aug 13 at 12:46