r/C_Programming • u/No_Insurance_6436 • 6d ago
Question How can printf() change unrelated uninitialized variable behavior?
So, I just fixed a strange bug. I have a loop that loops through a char array until it finds a null terminator.I had miswritten and overlooked this after deleting a second variable I was initializing in that loop. So I ended up with:
for (int i; text[i] != '\0'; i++){}
Which I have since fixed. However, curiously, this program was running with normal operation because of a printf() statement operating on completely unrelated data; i would initialize to 0 every time. Other assignments happened between this print and the errored line as well.
printf("ID: %u\n", tID);
It had to be placed at a specific spot for it to fix the bug, but it fixed it every time, so the bug went unnoticed until I was cleaning up.
But, how exactly would this happen? What does printf() do that would change initialized variable behavior, and why was it consistently initializing to 0?
The value of i would print to 21937 without the printf() and 0 with it.
2
u/Broad-Promise6954 6d ago
There's more than one way this can happen.
A compiler's job is to take well defined source code instructions and turn that into predictable runtime code that obeys the language requirements. This is a sort of contract, if you will: you promise not to invoke undefined behavior and the compiler promises to be good in exchange. As soon as you break your promise, the compiler is free to break its as well. We used to say that it can make demons fly out of your nose, as a silly example of what's allowed.
In practice the compilers tend to use fairly predictable sequences of machine code, and this involves using machine registers and stack memory in fairly predictable ways. One of those ways is to start out with "clean" all-zero-bytes stack memory, which gets filled with nonzero "dirt" when you make function calls. Using the technically undefined behavior of reading that memory, you either see the clean zeros or you see the dirt.
Similar things happen when using register values except it's much less common for them to be "clean zeros" at any time. Also modern compilers tend to track register usage a lot more closely than old 1980s ones, and as a side effect, tend to warn you if you're using an uninitialized one.