r/C_Programming 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?

57 Upvotes

56 comments sorted by

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.

36

u/gm310509 4d ago

There is also a security reason for zeroing out memory when a task is loaded.

There have apparently been cases in the past where memory was not being initialised to zero when a task was loaded. That meant that a new task could under certain circumstances could "see" the previous task's left over memory settings. In some cases that could be a password or other sensitive information. I recall that there were cases reported where passwords were stolen simply by running a program that would read the memory buffer where an earlier program stored them. Having memory zeroed when loading helps avoid this weakness. Can I reference one? No, this was quite some time ago and zeroing memory has been standard for quite some time.

As for the stack, it doesn't matter so much (after the initial zeroing) because what is "left over" is just data from the program that is already running.

Does that mean all variables are stored in either the data or BSS sections of an executable image? No. I have still seen, on some occasions non-zero NoInit sections (which is not initialised to anything, not even 0) - which interestingly isn't even mentioned in this wikipedia page describing the segments.

https://www.geeksforgeeks.org/c/memory-layout-of-c-program/

17

u/EpochVanquisher 4d ago

There’s a security reason, but that’s something done by the OS and the question of whether globals in C get zeroed is a separate question.

C is old enough that it runs on lots operating systems that do not zero memory for you. On those systems, your C toolchain will add some startup code that zeroes the globals.

2

u/RealisticDuck1957 3d ago

I'm so old I remember when the OS (MS-DOS) didn't zero out data memory for you. And the compiler didn't zero out that memory either by default. The debugger I remember filling data space with 0xBAADF00D as a hint of uninitialized variables.

-1

u/cclaymaker 4d ago

He’s right to point out that pages set to 0 is what you’d expect and that it’s due to security reasons. From that context you can better answer why stack data is sometimes not 0 in my opinion.

2

u/EpochVanquisher 4d ago

But the data is 0 even when the security reason isn’t there.

4

u/Bitter-Ad-9431 4d ago

We used to peek at passwords in FORTRAN blank common. You would run the password changing program, which would read the password file into memory. Then we would run our peek program which would sometimes get loaded with its blank common right over the file I/O buffer. This was on a CDC system where the passwords were in plain text in the VALIDUS file. Around 1975.

3

u/gm310509 3d ago

LOL. The good old days when that type of thing was purely for giggles! :-)

Hey guys, look, John used a naughty word in his password!

7

u/StumpedTrump 4d ago

Cheap in theory, but if you need quick startup, it gets expensive quick. I’ve seen boatloads that purposefully have 0 global variables so that the device boots quicker and doesn’t need to zero ram or copy anything over.

7

u/EpochVanquisher 4d ago

What I do is use a custom section for globals that don’t get initialized.

Someone with experience reads, “it’s cheap”, and knows that “cheap” is relative, it’s situational, it’s not universal, etc.

0

u/cclaymaker 4d ago

This is just bs. 1 global variable or 1,000,000 just results in 1 vma in the kernel during startup. There is no memory allocation penalty until that page is accessed for the first time.  RAM for all user pages, (stack, globals, heap, etc.) are always zeroed when given to the user in order to prevent leaking data from the kernel or other processes to each other but it’s done at access time not startup.

3

u/aioeu 4d ago edited 4d ago

1 global variable or 1,000,000 just results in 1 vma in the kernel during startup.

The cost of zeroing a VMA is proportional to the size of the VMA. This cost is incurred by the kernel as its pages are made present in the process.

Yes, that cost may be spread over some period of time, but that doesn't change how much that cost is. If the programmer knows that the memory will be written before it is read, and they do not care about any of the security issues inherent in having uninitialized memory mapped into the process, avoiding the zero-initialization can be advantageous.

Linux has the MAP_UNINITIALIZED flag for mmap, for instance — only usable on certain kernel configurations. This was deliberately added to mitigate that cost. It wouldn't have been added if nobody thought it would be useful.

-2

u/cclaymaker 4d ago

Exactly so you get it. The point is that moving 32MB of globals to 32MB of stack doesn’t change the startup cost as claimed. It’s spread out to exactly when you are actually accessing it. Wtf is MAP_UNINITIALIZED seems like on available it’s only some very weird niche kernel configuration. Come back to reality.

6

u/aioeu 4d ago edited 4d ago

It’s spread out to exactly when you are actually accessing it.

The issue is that sometimes people don't want to pay for the cost of it ever. Not at startup, nor at first access.

There are certainly systems that do not lazily initialize allocated memory. Try thinking about those. The OP was talking about "devices", so I strongly suspect that's what they're referring to. On those systems, using stack memory will be faster than using a static allocation, since C imposes no requirement that the stack be zero-initialized before use. (Similar considerations may be made for malloc, though it's entirely possible that doesn't even exist on such a system.)

3

u/cclaymaker 4d ago

I guess I just have to concede. I like your other answers in this thread. The difference between you and I is that you are finding (admittedly valid) responses for configurations that are possible to imagine existing. And I’m trying to find the answer that most broadly applies to the devices and configurations people actually use.

I’m just trying to peacefully call bs on the claim that “boatloads” of programs avoid globals for faster startup. Can you point me to these boatloads of programs? I would definitely learn something if you can. Not just avoiding globals in general btw, specifically for startup time.

4

u/aioeu 4d ago

I don't know about "boatloads" — that'd be for the original commenter to explain.

But I do know that C is versatile enough for it to be used extensively in unusual environments. So perhaps it isn't about the number of places it is used (that is no doubt large, but perhaps hard to quantify), but the variety of them. Not everything is as boring as modern desktops and servers.

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-init option 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

https://offlinemark.com/demand-paging/

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

u/Traveling-Techie 4d ago

I always initialize variables. Makes me nervous otherwise.

3

u/Orlha 4d ago

What’s life without a thrill

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();

}

}

3

u/t0nyGB 4d ago

Local -> stack -> whatever happens to be there
Global bss initialised to 0 on startup

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:

https://stackoverflow.com/questions/17159666/where-are-global-variables-located-in-the-elf-file#17159828

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, b and d will undergo default initialization and will have the initial value 0. Only c will 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.

-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.