If you have this code:
void Main()
{
    string name;
}
Then, when compiled (in LINQPad) with compiler optimisation you get the following IL:
IL_0000:  ret
And without optimisation:
IL_0000:  nop         
IL_0001:  ret         
There is no memory allocated for this declaration - just a NOP operation as a placeholder in the IL for the unoptimised code.
When your program is this:
void Main()
{
    string name = "Jack";
}
Then you compiler optimised code is:
IL_0000:  ret
The compiler simply ignores the unused variable.
The unoptimised code generates this:
IL_0000:  nop         
IL_0001:  ldstr       "Jack"
IL_0006:  stloc.0     // name
IL_0007:  ret         
Obviously the unoptimised code is more explanatory, so I'll only show unoptimsed code from now on unless I explicitly say otherwise.
Now let's make the code do something more interesting.
void Main()
{
    string name = "Jack";
    Console.WriteLine(name);
}
This produces:
IL_0000:  nop         
IL_0001:  ldstr       "Jack"
IL_0006:  stloc.0     // name
IL_0007:  ldloc.0     // name
IL_0008:  call        System.Console.WriteLine
IL_000D:  nop         
IL_000E:  ret         
What's interesting is if we change this code to this:
void Main()
{
    int answer = 42;
    Console.WriteLine(answer);
}
We get this:
IL_0000:  nop         
IL_0001:  ldc.i4.s    2A 
IL_0003:  stloc.0     // answer
IL_0004:  ldloc.0     // answer
IL_0005:  call        System.Console.WriteLine
IL_000A:  nop         
IL_000B:  ret         
The code is virtually the same as the string example.
The ldstr call is getting a reference to a string literal (which is store in the String Pool on the Large Object Heap (not the normal heap which is the Small Object Heap) and pushing it on the evaluation stack.
The ldc.i4.s is pushing a reference to the number 42 on to the evaluation stack.
Then, in both cases, stloc.0 is storing the value on top of the evaluation stack into the zeroth local memory location for the method.
Then, in both cases again, ldloc.0 is loading the value from the zeroth local memory location and putting it on the evaluation stack.
You can probably imagine what the compiler might do if it were optimising this code.
Finally the System.Console.WriteLine is made.
Now let's look at that pesky string code in more detail.
I said that it's stored in the Intern Pool. Let's check that.
Take this code:
void Main()
{
    string name = "Jack";
    Console.WriteLine(String.IsInterned(name));
}
It produces:
IL_0000:  nop         
IL_0001:  ldstr       "Jack"
IL_0006:  stloc.0     // name
IL_0007:  ldloc.0     // name
IL_0008:  call        System.String.IsInterned
IL_000D:  call        System.Console.WriteLine
IL_0012:  nop         
IL_0013:  ret
And it outputs Jack to the console. It can only do that if System.String.IsInterned returns an interned string.
Take this program to show the opposite:
void Main()
{
    string name = String.Join("", new [] { "Ja", "ck" });
    Console.WriteLine(String.IsInterned(name));
}
It pushed out null to the console - meaning that the string name isn't interned so in this case name is stored on the heap (Small Object Heap).
Let's look at your second bit of code:
void Main()
{
    for (int i = 0; i < 20; i++)
    {
        Run();
    }
}
private void Run()
{
    int age = 20;
}
If we look at optimised IL then the Run method looks like this:
Run:
IL_0000:  ret        
The unoptimised IL is this:
Run:
IL_0000:  nop         
IL_0001:  ldc.i4.s    14 
IL_0003:  stloc.0     // age
IL_0004:  ret 
And, like my earlier example with an int, it is loading the literal value 20 (or 14 in hex) onto the evaluation stack and then immediately storing it in the local memory for the method, and then returns. And hence it is repeating using the same memory 20 times for the local variable age.