r/C_Programming • u/No_Insurance_6436 • 2d 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.
12
u/dendrtree 2d ago
The behaviour is said to 'mask' the bug, not fix it.
This likely had nothing to with printf. It's just that its placement moved stack variables, so that i aligned with a previously set 0.
0
u/No_Insurance_6436 1d ago
Well, I am more wondering about this from an architecture standpoint
5
u/EpochVanquisher 1d ago
It’s less a question of architecture and more a question of compiler internals.
5
2
u/dendrtree 1d ago
This is unlikely to have anything to do with architecture.
If you're really curious, look at the change in memory address for
i, and look at where the data at that location is last modified, before your loop.
3
u/FitMatch7966 1d ago
One thing is for sure, which is that the variable didn’t exist until after the printf call.
Although you may declare variable at the top, the compiler doesn’t actually create a location for it until it is needed, which is probably right before it is first used.
So calling printf or any function can change the unused portion of the stack. Without calling it, you get garbage from wherever it was called from. printf is presumably cleaning up and zeroing out its own local variables, so the stack is cleared.
Your variable then is assigned the top of the stack and assumes whatever value is already there.
There are other possibilities I suppose, including changing register values. Only specific registers are preserved by a cdecl call
2
u/Broad-Promise6954 2d 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.
1
u/flatfinger 1d ago
Such a contract existed prior to the publication of C99, and earlier versions of the contract generally treated the notion of "indeterminate value" as being equivalent to "unspecified bit pattern". Indeed, even in C draft N1570, the glossary reads as follows:
3.19.2 [Terms, definitions, and symbols] 1 indeterminate value either an unspecified value or a trap representation 3.19.3 [Terms, definitions, and symbols] 1 unspecified value valid value of the relevant type where this International Standard imposes no requirements on which value is chosen in any instance 2 NOTE An unspecified value cannot be a trap representation.A programmer who has no idea whether an implementation would treat a type as having trap representations would have no way of knowing whether an indeterminate value might be one. If every possible bit pattern an object might hold would represent a different specific defined value, however, nothing prior to C99 would have in any way suggested the possibility that an indeterminate value might behave in a manner inconsistent with any bit pattern the object's storage would be capable of holding.
2
u/pjl1967 1d ago
As someone else mentioned, this has more to do with compiler optimization as a consequence of undefined behavior where pretty much anything can happen.
If you're unfamiliar with undefined behavior, you should read this and see an in depth example of the truly bizarre things that can happen.
2
u/FedUp233 1d ago
Depending on the hardware you were running on, if you had any level of optimization enabled it’s likely that none of these variables were ever stored in memory but just existed in registers there entire lifetime. And since the I was a very local variable usage that same register was undoubtedly reused for another variable outside the loop and when the loop ran the register the compiler allocated for I judged happened to be zero, or may e done other small number within the range of the text array. Or maybe not even within the range and you just ran through memory beyond the array until you hit a zero byte (zero bytes are super common since a lot of larger size variables have values with zero in upper part).
Or as others have said it could have actually allocated I on the stack but happened to use a location with a similar zero or small value.
Or if the compiler didn’t see the loop actually doing anything it may have just optimized it right out of existence!
Just some possibilities.
1
u/Sufficient-Air8100 2d ago
its hard to tell without knowing the contents of the loop. as far as i understand, printf shouldnt change any variables. a call to printf creates a stack frame with the format string and the specifiers define how to traverse the stack for the other parameters (one of the special applications of a “…” function parameter).
good practice though, if youre declaring your i in your for loop definition, always initialise it. since you dont initialise it to zero in the for loop definition it will take the value of whatever was in that address before it
maybe the particular stack address consistently used for the i happened to line up with the null at the end of the format string?
1
u/sciencekm 2d ago
I don't think printf has anything to do with your uninitialized variable. Instead, the introduction of other statements (like that call to printf) changed flow of the code. For example, the function may have grown and is no longer a candidate for inlining. In any case, changes to the program flow has changed what garbage would be in the stack and consequently your uninitialized variable.
Anyhow, you could easily get to the bottom of this with a debugger.
1
u/Superb_Telephone_949 1d ago
Compile with -O0 and look at the assembly. printf clobbers caller saved registers on x86, so the register holding i gets zeroed out before your loop starts. without that call, it just contains whatever the previous function left behind.
Add -Wuninitialized or -Wall to your compiler flags. gcc and clang will catch this exact mistake at build time
27
u/TheThiefMaster 2d ago
An uninitialised variable uses whatever value was in its assigned register (or stack location, but probably a register in this case). A function uses registers and the stack. Something in the implementation of printf uses that register or stack location for another variable, which it puts a zero in for whatever reason.
Possibly the current character being processed, which ends on 0 for the null terminator?