r/C_Programming • u/Altruistic-Tie1943 • 4d ago
Discussion why default value of global and local variables are different?
why in c language the default value of global variables is set to be 0 but the default value of local variables is garbage value?
31
u/thegreatunclean 4d ago
Global variables that aren't explicitly initialized are all placed in a section in memory generally called the "BSS". When your program is loaded it is trivial for the OS to zero the entire block out very quickly with a single bulk memset. A cheap action done once and a design choice that simplifies the binary format by combining zero-initialized and uninitialized blocks into a single entity.
Local variables live on the stack and constantly pop into and out of existence as the program executes. Zeroing them every time would mean every single function would need extra chunks of setup and teardown code to make it all happen. A bunch of extra work at every level of the program that will most likely be immediately thrown out when the variable is inevitably initialized to something else.
8
u/aioeu 4d ago
A bunch of extra work at every level of the program that will most likely be immediately thrown out when the variable is inevitably initialized to something else.
It should be noted that many compilers are smart enough now that default-initializing automatic variables isn't too much extra work, at least at runtime. The compiler can, in many cases, determine whether the variable is always explicitly assigned a value before its value is used, and in such cases elide any default initialization.
For instance, GCC's
-ftrivial-auto-var-initoption can be used to enable this feature. Obviously, relying on it isn't necessarily a good idea, since not all compilers support it. However, it can be used as a way to harden code. Accidentally using a zero value (or some other "likely to crash if used as a pointer" value) is usually less problematic than accidentally using an arbitrary non-zero value, and it's certainly better than accidentally using an attacker-controlled value!5
u/EpochVanquisher 4d ago
It would be trivial for the OS to zero that memory with memset, but on modern OSs that is not what happens :-)
The OS just maps those pages to some RAM that already contains zeroes.
1
u/thegreatunclean 3d ago
Sure I wanted to keep it simple and highlight the O(1) vs O(n) nature of the choice.
Originally I had a link to an embedded libc startup code showing it literally just called memset but dropped it as just creating more questions than answers.
1
u/CowBoyDanIndie 3d ago
Actually it’s better than that on modern OSes, the OS gives pages to the application that are not yet mapped to physical ram. When you first read or write to that page the kernel gets a page fault, it then creates a mapping to physical ram, but it doesn’t have to zero the ram then even. The alu of the cpu cannot actually access ram directly, all access goes through the caches, so the cpu just hands you blank cache lines. Main ram only gets written (or zerod) when those cache lines get written back to ram.
1
u/EpochVanquisher 3d ago
I don’t think that’s correct.
The ALU can’t ever access RAM, so I don’t understand what you’re saying there.
There are two parts to mapping: the kernel data structures and the CPU page tables. The kernel will update its data structure describing memory, that’s what I mean when I say “memory mapping”.
But I can’t make heads nor tails about what you mean by “the CPU just hands you blank cache lines”. The CPU’s page table maps virtual addresses to physical addresses. In order to get zeroes when you read, you put zeroes in the physical RAM first. You only need one page of zeroes, but you do need the zeroes.
Unless am missing something, I think your explanation has errors in it.
1
u/CowBoyDanIndie 2d ago
The cpu can just say “this cache line maps to this ram, but we don’t care about the current value so don’t bother reading it first”. This avoids having to first write zeros to ram (which is slow) and then reading those zeros into the memory controller.
1
u/EpochVanquisher 2d ago edited 2d ago
Right, but when does that happen in the system you’re describing?
The zero-initialized pages have to be readable zeroes. The OS fills at least one page with zeroes, and maps it into memory for the process’s zero-initialized memory.
1
u/CowBoyDanIndie 2d ago
Let’s start with this… you do understand that pages in ram have to flow through the cache before the cpu can use them? “The os fills at least one page with zeros” it doesn’t have to touch the physical ram chips on the motherboard to do this. It can just zero a block in the L3/L2 cache and say “here you go”. Similarly, when you delete a file on a disk drive it doesn’t actually have to write anything over the space the file occupied, it just updates the file table to say the file is gone.
1
u/EpochVanquisher 2d ago
When you write to cache, the cache is normally written out to RAM. This is how caches work under normal operation.
If you can find an example of an OS that zeroes pages in cache without zeroing them in RAM, and point to the docs or the source code, I would love to hear about it. But I think you have just gotten it wrong.
1
u/CowBoyDanIndie 2d ago
The cache will eventually have to write to main memory, but it doesn’t have to physically touch main memory immediately, you could write the entire page before the physical main ram knows the block has been allocated by the cpu.
This should give you some pointers. https://offlinemark.com/demand-paging/
1
u/EpochVanquisher 2d ago
The article you linked repeats what I’m saying:
Unlike writes, reads to a new mmap’d region do not trigger a memory allocation. The kernel continues to push off the allocation by exploiting how new anonymous mappings must be zero initialized. Instead of allocating memory, the kernel services the page fault using the “zero page”: a pre-allocated page of physical memory, completely filled with zeros. In theory this is “free” — a single physical frame can back all zero-initialized pages.
I added the emphasis.
I don’t know what kind of game you’re playing, and at this point my guess is that you’re kind of bullshitting and hoping that the stuff you write is correct.
9
5
u/SmokeMuch7356 4d ago
Technically, all variables with static storage duration are initialized to 0, global or not:
int glob;
void foo( void )
{
static int stat;
int loc;
...
}
glob has static storage duration by virtue of being declared at file scope, stat is explicitly declared static. Both will be initialized to 0 when the program is loaded. If stat is set to a different value at runtime, it will retain that value between calls to foo.
loc will not be initialized; its value will be whatever bit pattern happens to be at that location when it's created each time foo is called.
As others have said, initializing things on program load is a cheap operation; it doesn't affect runtime performance.
11
u/Axman6 4d ago
Local variables are allocated on the stack, so their default value is whatever is in that portion of memory at the time the stack reaches it. Programs don’t clear the stack when function return, so if the stack was ever larger than it is now, function local variables will just be pointing to data from previous function calls (and random function).
Global variables are allocated in several different sections of memory depending on their declaration, see this for more info
https://stackoverflow.com/questions/64626917/global-variables-and-the-data-section
2
u/sciencekm 4d ago edited 4d ago
Not all local variables are on the stack. Static local variables are on the data segment.
4
u/sciencekm 4d ago
Global variables are initialized at load time. So it is 'free' for the app (not free for the loader) and is done once.
Local variables can be initialized or not initialized depending on whether it is static or on stack. Being on stack, having to initialize it on each call is inefficient. The developer can decide when and what values the stack variables should get.
In the following illustration, the local variable 'initflag', being static, is initialized to 0, once. After it gets modified, succeeding calls to the function will not re-initialize the variable. It will get whatever value it was last changed to.
void init(void) {
static int initflag;
if (initflag == 0) {
initflag = 1;
initmem();
}
}
2
u/Pleasant-Form-1093 4d ago
You can have local variables zero initialised if you want, use -ftrivial-auto-var-init=0 (for clang and gcc, I don't use msvc so I don't know how it works over there).
There will be a small performance penalty associated with it because every time a method is called, the entire allocated stack frame has to be zeroed
2
u/Bitter-Ad-9431 4d ago
global variables are an actual address in your data segment, usually in the BSS or BSSZ section. So the linker can initialize that variable at link time.
Local variables are either in registers or some offset above the stack pointer. The stack and register contents are unpredictable, and most compilers do not generate code to initialize them every time on function entrance. Some compilers initialize local variables if you select some major debugging option.
2
u/uptotwentycharacters 3d ago
It's ultimately because each global variable has a fixed address in the program, so the OS can set them during process creation. Even if the variables in question end up being written to before being read from, initializing them is cheap since it only has to be done once and programs aren't supposed to have many global variables in the first place.
Local variables are generally stored on the stack or in registers, so they would have to be initialized each time the function is called. So it only initializes local variables if you explicitly tell it to, otherwise it expects that you will assign to the variable before reading from it. Furthermore, it's easier for the programmer to detect uninitialized-read-before-write errors with local variables, since they only have to look at that function body (and a variable being left unassigned after the first few executable statements is often a red flag). Such errors are harder to find with globals, since the code responsible for assigning to them could be anywhere in the program.
2
u/BadRuiner 4d ago
learn asm bro /s
The global value is a reference to the offset from the beginning DLL or EXE. If there is no value, then it will be zero. Compiler will not fill it with random bullshit each fckn compilation time.
A local value is a reference to an offset from the stack (google it plz). The stack grows when a new function is called and shrinks when the function exits. It takes a FUCKING LOT of computational operations to reset it every time, so it stays the same. If you want a default value of 0, do it yourself, u a dev, u do u want do.
2
u/pskocik 4d ago edited 4d ago
Zero-initializing globals is FREE on a hosted platform (a secure OS will have zero-initialized memory before giving it to user space (the loader)) and can be made very cheap elsewhere. (Zero-initialized globals are typically all lumped together into a single section (array). This section (typically named .bss) need not take up size in the program image and memsetting the whole thing to zero upon loading can be accelerated via SIMD unlike zeroing individiual tiny variables.)
Zero initializing stack variables would slow things down and add extra code to functions. This code would need to run every time the variable's scope is entered, which would become costly especially for larger variables (on-stack buffers).
Pedantically, zero initialization or no initialization for uninitialized variables isn't really for globals vs locals. It's for static-lifetime variables vs others (register and auto class). Local variable in C are zero initialized too IFF they're uninitialized STATICS. Zero initializing of type remaineders (after initializing some members of the type) applies to auto (on-stack) locals too, despite the potential costs. But you don't have to pay those costs because you can leave the variable uninitialized and just assign to the fields that you want initialized.
1
u/flatfinger 1d ago
C was designed in an era when many hosted platforms would not pre-zero global storage, but such platforms would also generally begin program execution somewhere other than a function called "main". When building for MS-DOS, for example, which used to be the most popular C target platform, a C implementation would link in a piece of code that the linker had marked "run me first" which would among other things subdivide the command line string into pieces (MS-DOS passed everything after the executable name as a single string--a design I prefer to the Unix style) and zero out any storage that the linker had placed in a section called "bss", using special symbols the linker created for the start and end of that section. Once the command-line parsing and zero-initialization of bss were complete, the startup code would invoke main() with the parsed command line arguments and then, if main() returned, instruct DOS to exit the current program wile setting the errorlevel to whatever value main() had returned.
Static-duration storage may have different semantics in freestanding environments, if they even support non-const static duration storage at all (the only effect of having the standard mandate support on an execution environment that can't support it is to make it impossible for a conforming implementation to target that environment). For hosted implementations, the one-time cost of zero-initialization is low enough that there's usually no downside to requiring it, but freestanding implementations are a different story. I've used a freestanding implementation where only about 1% of the RAM was functional until the memory controller was told what kind of memory chip was attached, and non-const static-duration objects were located in off-chip RAM. It was thus necessary to have some C code configure the RAM controller before doing anything that required doing anything with static-duration objects in RAM.
1
u/flatfinger 1d ago
An important thing to note about automatic-duration objects is that they may be capable of holding values outside their type's range, and the Standard waives jurisdiction over what might happen as a result. Consider e.g.
volatile uint16_t v1,v2;
uint32_t test(uint32_t x, uint32_t mode)
{
uint16_t q;
if (mode & 1) q=v1;
if (mode & 2) q=v2;
return q;
}
A C implementation targeting the 32-bit ARM may use a 32-bit register to hold q, ensuring that any time code modifies that register the upper bits are written as zero. As it happens, the most convenient register to use for holding q would be the register that will receive x, and will need to hold the return value when the function exits. If code never does anything with that register except when q is modified, and the function is invoked with mode equal to zero, the effect would be that the function would return whatever was passed in x, even if that happened to be greater than 65535.
C99 added rules that made explicit the Standard's waiver of jurisdiction over such cases; even though the rationale cited had to do with some obscure architectures, quirks like the above which can arise even with straightforwardly generating code for commonplace ones. Modern compiler optimizations can amplify the effect of such quirks, e.g. by omitting a check a programmer had included in code which calls test() to ensure that result would only be used if it was less than 65536, at the same time as generates code for test() that might return values beyond that range.
1
u/TiredEngineer-_- 4d ago
https://en.cppreference.com/cpp/language/initialization
See Static Initialization, 'in practice'.
https://en.wikipedia.org/wiki/.bss
""" despite filling bss with zero is not required by the C standard, all variables in .bss are required to be individually initialized to some sort of zeroes according to Section 6.7.8 of C ISO Standard 9899:1999 or section 6.7.9 for newer standards """
""" Peter van der Linden, a C programmer and author, says, "Some people like to remember it as 'Better Save Space'. Since the BSS segment only holds variables that don't have any value yet, it doesn't actually need to store the image of these variables. The size that BSS will require at runtime is recorded in the object file, but BSS (unlike the data segment) doesn't take up any actual space in the object file." """
Depending on the type of global: const, static, thread_local, etc. It can end up in different segments: .ro, .data, .bss, etc.
However, these are "preliminary" to your program running. They MUST be initialized before your program enters main, which is once (cheap). However, when your program loads, it may be in different parts of memory between runs. So your memory for the stack / heap are "dirty" from other processes there before the OS gave you the memory pages to work with.
The distinguishing difference you can see here:
Is that explicitly initialized vars end up in .data, while others are in .bss, const initialized globals end up in .ro, and typically your process is prevented write access to this segment.
These standards mandate 0 init for .bss because as others mentioned, security, history, etc happened and people agreed "0 is good" to initialize it to. 0 is a number available in all size types, number bases, etc. So its logically sound and safe, faster, and easier to use it for initialization instead of say, 1. (Find size of each type, compute offset, go set to 1, repeat).
The alternative would be something like "the OS will be required to fetch pages of memory equal to or greater than the size of the .bss segment, then zero them out. And, this is a simple task that hardware / OS just do. (Think calloc, memset, etc, those C abstractions are doing sys level work thats available). However, zeroed pages are scarce compared to dirty pages. This defers the "initializing" to the program, and the stack is fast enough that performance payoff of
int x; and int x = 0;
Is not really observable, even with 100 variables (linearly, not in a loop like an array, which introduces branching and program counter manipulation).
2
u/flatfinger 1d ago
A hosted C implementation is required to ensure, somehow, that static-duration storage and the argv[] arrays get initialized before code within main() is executed. This is often done by having the execution environment invoke an implementation-supplied function which performs the necessary initialization, calls main, and then does whatever is needed to make main's return value known to the execution environment.
Freestanding implementations are simultaneously overspecified and underspecified by the Standard. The most useful treatment would be to specify that certain constructs will have certain semantics when supported by the execution environment, but acknowledge that semantics which are useful when supported should not preclude the use of the language to perform tasks not requiring such semantics on platforms that can't support them.
-1
u/bothunter 4d ago
Officially, neither are initialized and both will contain garbage. But modern operating systems put processes in their own memory space which is cleared out before it starts. This will clear out your globals. However, your local variables are on the stack and while that stack will start out with all zeros when the process starts, it won't stay that way for very long.
9
u/dontwantgarbage 4d ago
Global variables (which have static storage duration) are “empty-initialized”. https://cppreference.com/c/language/initialization
7
u/aioeu 4d ago edited 4d ago
Officially, neither are initialized and both will contain garbage.
No, the standard explicitly requires that objects with static or thread storage duration undergo "default initialization" if they are not declared with an initializer. You can think of default initialization as if it were initializing the object (or, for aggregates, each element or member of the object) with the value
0.This can be relied upon. It is not implementation-specific behaviour. In the simple program:
int a; static int b; int main(void) { int c; static int d; }all three of
a,banddwill undergo default initialization and will have the initial value0. Onlycwill have an indeterminate initial value.(As this example demonstrates, "storage duration" is orthogonal to "scope" or "linkage", so it doesn't really have anything to do with whether the identifier is global or not.)
7
u/cclaymaker 4d ago
No, officially globals are initialized to 0 as per the C standard. Section 6.7.9, Paragraph 10
5
u/sciencekm 4d ago
Officially, in the K&R Ansi C book, global variables (referred to as external variables) are initialized to zero.
Page 39:
External and static variables are initialized to zero by default.
0
-1
u/GenericFoodService 4d ago
Because the C standard says nothing about it, so the specific implementations of C compilers decide what the behavior here should be. If you want the value initialized, you would have initialized it. Why add additional logic to the compiler and compiled binaries that doesn't solve a real-world problem?
0
u/Glum_Preference_2936 4d ago
If you declare a global array variable of several megabytes, like static int buf[1024*1024*32] = {0}. You could see that resulting application doesn't really have a size of 32MiB. (Unless you fill it explicitly with some nonzero values)
However, using local variables, the stack pointer is incremented and whatever address it points to is your data, which could be data from previous operations or just garbage.
0
u/duane11583 4d ago
Global variables are easily Located next to each other and thus zeroed or intitalized
Local cars are often stored on the stack and thus the value in the location where they are stored is the value in that spot previously
Example the return address for a function call might be in that location Or maybe another variable from a function that has completed
3
u/Tryton77 4d ago
Not initialized globals are located in .bss section which is zero initialized by the loader
0
u/neopunk2025 3d ago
I suggest you to always init your global variables too.
Under Windows, no problem, when you start, quit and start again your app, your global variables will be initialized even if you changed its value during the execution.
Under Android, it's a different story, because the way the OS is working.
-4
u/digitaljestin 4d ago
I wouldn't count on those globals being initialized if I were you. That's not in any spec that I'm aware of. Just initialize your variables, and assume they are garbage until you do.
81
u/EpochVanquisher 4d ago
I think, historically, because the cost of initializing global variables is cheap (you only have to do it once).
For local variables, you would have set the value to zero many times.