(gdb) n
134 a = b = c = 0xdeadbeef + ((uint32_t)length) + initval;
(gdb) n
(gdb) p a
$30 = <value optimized out>
(gdb) p b
$31 = <value optimized out>
(gdb) p c
$32 = 3735928563
It means you compiled with e.g. gcc -O3 and the gcc optimiser found that some of your variables were redundant in some way that allowed them to be optimised away. In this particular case you appear to have three variables a, b, c with the same value and presumably they can all be aliassed to a single variable. Compile with optimisation disabled, e.g. gcc -O0, if you want to see such variables (this is generally a good idea for debug builds in any case).
If you are interested in a particular variable in gdb, you can delare the variable as "volatile" and recompile the code. This will make the compiler turn off compiler optimization for that variable.
Minimal runnable example with disassembly analysis
As usual, I like to see some disassembly to get a better understanding of what is going on.
In this case, the insight we obtain is that if a variable is optimized to be stored only in a register rather than the stack, and then the register it was in gets overwritten, then it shows as <optimized out> as mentioned by R..
Of course, this can only happen if the variable in question is not needed anymore, otherwise the program would lose its value. Therefore it tends to happen that at the start of the function you can see the variable value, but then at the end it becomes <optimized out>.
One typical case which we often are interested in of this is that of the function arguments themselves, since these are:
always defined at the start of the function
may not get used towards the end of the function as more intermediate values are calculated.
tend to get overwritten by further function subcalls which must setup the exact same registers to satisfy the calling convention
since i is the first argument of f1, and therefore stored in RDI.
Now, while we were at both:
i += 1;
j += f2(i);
the value of RDI hadn't been modified, and therefore GDB could just query it at anytime in those lines.
However, as soon as the f2 call is made:
the value of i is not needed anymore in the program
lea 0x1(%rax),%edi does EDI = j + RAX + 1, which both:
initializes j = 1
sets up the first argument of the next f2 call to RDI = j
Therefore, when the following line is reached:
k += f2(j);
both of the following instructions have/may have modified RDI, which is the only place i was being stored (f2 may use it as a scratch register, and lea definitely set it to RAX + 1):
and so RDI does not contain the value of i anymore. In fact, the value of i was completely lost! Therefore the only possible outcome is:
$3 = <optimized out>
A similar thing happens to the value of j, although j only becomes unnecessary one line later afer the call to k += f2(j);.
Thinking about j also gives us some insight on how smart GDB is. Notably, at i += 1;, the value of j had not yet materialized in any register or memory address, and GDB must have known it based solely on debug information metadata.
Therefore, it is easy for GDB to find the values of those variables at any time: they are always present in memory!
This also gives us some insight on why it is not possible to avoid <optimized out> in optimized code: since the number of registers is limited, the only way to do that would be to actually push unneeded registers to memory, which would partly defeat the benefit of -O3.
Extend the lifetime of i
If we edited f1 to return l + i as in:
int __attribute__((noinline)) f1(int i) {
int j = 1, k = 2, l = 3;
i += 1;
j += f2(i);
k += f2(j);
l += f2(k);
return l + i;
}
then we observe that this effectively extends the visibility of i until the end of the function.
This is because with this we force GCC to use an extra variable to keep i around until the end: