EEVblog Electronics Community Forum

Products => Computers => Programming => Topic started by: ricko_uk on November 06, 2021, 03:34:42 am

Title: Global vars initialisation at declaration or later
Post by: ricko_uk on November 06, 2021, 03:34:42 am
Hi,
I always initialized global variables at definition because that way I know that from the very first line of code execution.

I don't see any reason for doing otherwise (i.e. initialising them in the code) but most likely there are some. What are they? Why would someone initialise global variables at any other point in the code other than declaration?

Thank you
Title: Re: Global vars initialisation at declaration or later
Post by: floobydust on November 06, 2021, 03:52:18 am
Don't assume the variables stay pure and never get corrupted, like in a world of unicorns and rainbows. One soft error or the usual pointer punching a hole somewhere, it does happen.

I initialize variables right before use, which also allows error recovery code to simply re-execute an init routine anytime, like after the start of execution. I assume all my variables are garbage and assign them initial values as needed.
I guess it's a habit from the days of coding in assembly. Assuming they are all zero, for example seemed a bit risky but people rely on the compiler to add that code nowadays.
With globals though, during run-time I range-check them and if out, re-initialize them, to recover from soft errors.
Title: Re: Global vars initialisation at declaration or later
Post by: Psi on November 06, 2021, 04:03:04 am
Another reason.

You might set a global variable with a no-init compiler attribute/pragma and then not init the variable. This is useful if you want some variable contents to stay intact between user and watchdog reset but you want to initialize the variable if you detect a power on reset since that will have corrupted the SRAM.

Also you might set a global variable at a fixed memory address so that the bootloader app and the main app can both use the variable to check status info when you move between the app and bootloader.

Title: Re: Global vars initialisation at declaration or later
Post by: TheCalligrapher on November 06, 2021, 05:00:07 am
I don't see any reason for doing otherwise (i.e. initialising them in the code) but most likely there are some. What are they? Why would someone initialise global variables at any other point in the code other than declaration?

For some reason, you forgot to state what programming language your question is supposed to be about. The reasoning and the terminology can be quite language-dependent.

If this is about C and/or C++, then there's a terminological issue in your question. It is not possible to initialize variables anywhere else, but at the point of declaration. Initialization is always a part of declaration. You can't "initialize" variables later, neither global nor any other kind. Global variables are always initialized for you, regardless of whether you supplied explicit initializers in their declarations. If you don't supply an initializer, a global is default-initialized anyway.

What you might do later is not "initialization", but rather assignment, reassignment, modification - whatever you want to call it.

As for the possible reasons to delay assigning a meaningful value to your global variable till later... some of them are rather obvious: you simply don't always have a meaningful value for your global variable at the point of declaration. That meaningful value might require some steps to build. Steps that you will carry out once your program starts.

In C you are not allowed to initialize global variables with anything besides constant expressions. So, if your variable needs a value that's not a constant expression, you will have no choice but to assign it later.

In C++ you have very limited control over the order of global initialization. And sometimes that level of control is insufficient. So, in order to do that in more predictable manner, you might want to assign your variables later, in explicitly step-by-step fashion.

Title: Re: Global vars initialisation at declaration or later
Post by: Nominal Animal on November 06, 2021, 05:26:29 am
Wrt. terminology, by "initialisation", we are talking about simply setting the initial value, and not using the term as defined in the C or C++ standards.

Why would someone initialise global variables at any other point in the code other than declaration?
On resource-constrained systems like microcontrollers, initialising global variables in C and C++ is done as part of the system bootup, and currently involves either copying the (nonzero) data from ROM/Flash, or executing a function (implemented by the compiler) to initialize the object (in C++).

In many cases, the objects can be initialized more efficiently using dedicated expressions.  For example, you might have an array where all entries are initialized to the same pattern.  So, instead of using the standard language-provided facilities to set the initial value, you sometimes use a separate function to do so, to save ROM/Flash.

There is also a much more complicated situation: reboots due to watchdog et cetera.  If the device gets rebooted, typically the contents of the RAM are retained.  However, since it can be difficult to tell why a reboot occurred in the first place, the contents may not be valid (for example, because they were garbled due to a programming error).
If you use a separate function to set their initial values, it is possible for that function to check e.g. a CRC of the value (which is updated whenever the variable is updated), and determine whether the variable is still valid, and not rewrite it with the bootup default.  Sometimes this can let the system continue after crash with minimal user-visible effects, but more often that kind of information is used to log the state at which the crash occurred.  There isn't enough resources for the runtime to do that automatically (the closest equivalent is the core dumps on POSIXy systems when running an application or service that crashes, generating a file that describes the state of the process at the point it crashed), but if there is a global state variable or a set of global state variables, detecting a crash while setting their initial values (and perhaps populating "crash log" shadow state variables?) is another reason to do variable and object initialisation separately.

None of the reasons I can think of extend to C or C++ in general, or in hosted environments (with full standard libraries available, say running under an OS).  This all assumes an embedded and/or resource-constrained environment, and the technical needs of such; and in a very real sense, really is independent of the programming language used.
Title: Re: Global vars initialisation at declaration or later
Post by: SiliconWizard on November 06, 2021, 06:42:17 pm
Hi,
I always initialized global variables at definition because that way I know that from the very first line of code execution.

I don't see any reason for doing otherwise (i.e. initialising them in the code) but most likely there are some. What are they? Why would someone initialise global variables at any other point in the code other than declaration?

One obvious reason would be for initialization values that would depend on other values/inputs that can only be known at run-time. That happens. And if you're gonna initialize some variables at run-time, giving them an initializer in the declaration is wasteful.

For other cases, it's usually more efficient to give initializers in the declarations. The startup code usually copies the initial values in just one big block in an efficient way, so it just takes less code size and executes faster.

OTOH, do not give initializers to variables not needing any, as I said. That's wasteful. without one, global variables will be guaranteed to hold all zero values.
Title: Re: Global vars initialisation at declaration or later
Post by: ricko_uk on November 06, 2021, 07:15:56 pm
Thank you all, all valid points. :)
Title: Re: Global vars initialisation at declaration or later
Post by: T3sl4co1l on November 06, 2021, 11:07:52 pm
Or the other reading, which it seems isn't intended, and is fairly trivial:

Global vars declared without initialization, are a fine way to share them between modules.  Usually with an extern keyword.  (Conversely, static keeps them confined to the module, even if a variable of the same name is used elsewhere.)

Whichever instance of the shared variable has a nonzero initializer, will be picked for linking.  Hmm, I think?  Not sure if you can force initialize to zero specifically, and hopefully there's an error if multiple initializers exist.  I've never actually tried it, heh.

As for initialization in general, in C, variables without are guaranteed zero -- you'll find them placed in the .bss section, which is cleared by the .start section (which also copies init into .data).  There is no "noinit" officially (I think?), but many compilers provide an attribute/pragma to allow that.  Or you can override start() if you really want.  This being the embedded experience of course; typically a hosted (OS) environment does all of this on loading the overlay.

Tim
Title: Re: Global vars initialisation at declaration or later
Post by: ricko_uk on November 07, 2021, 01:47:48 am
Thank you Tim :)
Title: Re: Global vars initialisation at declaration or later
Post by: TheCalligrapher on November 10, 2021, 05:32:38 am
Global vars declared without initialization, are a fine way to share them between modules.  Usually with an extern keyword. 

... which is non-standard. If you want to stick to real C, then proper usage of `extern` is mandatory. You have to make sure your program provides only one definition of the variable.

In C++ the situation is similar, but since C++17 this task is greatly simplified by declaring global variables as `inline`. 

Whichever instance of the shared variable has a nonzero initializer, will be picked for linking.  Hmm, I think?  Not sure if you can force initialize to zero specifically, and hopefully there's an error if multiple initializers exist.  I've never actually tried it, heh.

Exactly. If you rely on the aforementioned non-standard extension, then whichever instance of the shared variable has an explicit initializer, will be picked for linking.

Title: Re: Global vars initialisation at declaration or later
Post by: T3sl4co1l on November 10, 2021, 10:04:32 am
Hmm, what's nonstandard about it?  This seems to explain it pretty normally,
https://en.cppreference.com/w/c/language/extern

Tim
Title: Re: Global vars initialisation at declaration or later
Post by: TheCalligrapher on November 10, 2021, 12:46:38 pm
Hmm, what's nonstandard about it?  This seems to explain it pretty normally,
https://en.cppreference.com/w/c/language/extern (https://en.cppreference.com/w/c/language/extern)

Um... Not sure what you mean. The "One definition rule" section there is pretty clear about the fact that you are allowed no more than one definition of an object with external linkage in your program.



If you want to learn more about the history of the issue, take a look at section 6.2.2 in C99 Rationale (http://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf).

Unix compilers historically adhered to Relaxed Ref/Def model. The word "relaxed" actually refers to the fact that you are allowed to have multiple definitions of global objects.

However, K&R rejected Relaxed Ref/Def model and decided to adhere to Strict Ref/Def model, in which no more than one definition is allowed. This decision was made to recognize the fact that on other (non-Unix) platforms the task of recognizing and merging multiple definitions between translation units is an overly heavy burden. Later, the standard inherited this Strict Ref/Def model and also borrowed some details from Initialization model.

Nevertheless, some C compilers still adhere to Relaxed Ref/Def model as a non-standard extension, which is explicitly listed as a "common extension" in section J of the language standard: http://port70.net/~nsz/c/c11/n1570.html#J.5.11 (http://port70.net/~nsz/c/c11/n1570.html#J.5.11)
Title: Re: Global vars initialisation at declaration or later
Post by: T3sl4co1l on November 10, 2021, 03:48:06 pm
Well yeah I said to use one initializer.  So I'm just not sure what was nonstandard about that.  Sounds like it was just misread (or more likely: poorly phrased).

Tim
Title: Re: Global vars initialisation at declaration or later
Post by: TheCalligrapher on November 10, 2021, 04:31:01 pm
Well yeah I said to use one initializer.

That's not enough. Initializers do not matter, actually.

If you have several translation units with

Code: [Select]
int a; /* no initializer */

in them, your code is already violating the One Definition Rule.

The above is a tentative definition. However, once the translation unit ends, it will implicitly produce a regular definition, equivalent to

Code: [Select]
int a = 0;

(see http://port70.net/~nsz/c/c11/n1570.html#6.9.2p2 (http://port70.net/~nsz/c/c11/n1570.html#6.9.2p2)). Each translation unit will produce a regular definition of `a`, and you will end up with multiple definitions of `a` in the program, thus violating ODR.

However, some C implementations allow this. That's exactly the extension J.5.11 is talking about.



A lot of people fall into the "tentative definition" trap. They believe that just because the above is a tentative definition, they can have as many of those as they want in their entire program. That's not true. Tentative definition is a local concept, local to each translation unit. Thanks to tentative definitions you can have this

Code: [Select]
int a;
int a;
int a, a, a, a, a;

in one translation unit. The compiler proper shall not complain.

However, once the compiler gets to the end of the translation unit, it is required to convert these tentative definitions into a regular definition, i.e. emit a single regular definition of `a` for this translation unit.

And if you have multiple translation units with tentative definitions of `a`, each of them will emit a regular definition of `a`. This violates the standard ODR. The linker is supposed to complain.

Title: Re: Global vars initialisation at declaration or later
Post by: T3sl4co1l on November 10, 2021, 05:26:59 pm
Ah, okay.  Then the use case with extern int a; in (shared) headers, is fine, and int a[ = <initial value>]; is only done in the, wherever module it comes from.

I was a bit confused by this recently, as I had a project that apparently compiled and worked normally, with only tentative definitions in (shared) headers, in whatever flavor of compiler (and settings) comes with Cube; but stock GCC generated errors (so that'd be strict model, then).  Evidently it was compiled with whatever settings to permit the relaxed model.  Well, I changed it anyway (to use extern), which seems to be an improvement.  Excellent. ;D

Tim
Title: Re: Global vars initialisation at declaration or later
Post by: DiTBho on November 10, 2021, 06:19:27 pm
In my projects, you always see something like

global_init();
${app_name}_init(p_obj);  /* object' vars initialization */
Title: Re: Global vars initialisation at declaration or later
Post by: SiliconWizard on November 10, 2021, 06:20:38 pm
tentative definitions are a bitch anyway and very bad style IMO. Don't do that. They can only confuse anyone reading the code, for very rarely a good reason.

Title: Re: Global vars initialisation at declaration or later
Post by: TheCalligrapher on November 11, 2021, 07:15:42 am
tentative definitions are a bitch anyway and very bad style IMO. Don't do that. They can only confuse anyone reading the code, for very rarely a good reason.

Not sure what you mean here.

When people want to provide a definition for a global variable in C, they often just do

Code: [Select]
int global;

in some (one and only one) translation unit. They know that the variable will be zero-initialized by default and they are happy with that. End of story.

But formally, this is a tentative definition.

It is not like these people wanted to use a tentative definition specifically. They just wanted to define a variable. It works the way they want to, and that is all they care about. Overwhelming majority of them haven't even heard about "tentative definitions".

So, what do you mean by tentative definitions' being "a bitch"? How would the above "confuse" anyone?

The only case where you are actually using tentative definitions in any material way is when you do

Code: [Select]
int global;
...
int global;

i.e. define the same variable many times in the same translation unit. But normally nobody does that. If only by mistake...

---

Come to think of it, my attempts to come up with a good non-contrived material use case for tentative definitions come up empty. Maybe someone here knows a good one?

Re-reading the C99 Rationale it appears that the feature was introduced to support some ancient pre-standard declaration-defintion approaches for `static` variables. I.e. it is a feature that simplifies support of non-standard legacy code. Any material use is redundant in standard code. Which is why I can't come up with an example.
Title: Re: Global vars initialisation at declaration or later
Post by: DiTBho on November 11, 2021, 11:55:53 am
global_init() in my case is a function that initializes all the global variables.
Title: Re: Global vars initialisation at declaration or later
Post by: DiTBho on November 11, 2021, 12:07:34 pm
There is also a much more complicated situation: reboots due to watchdog et cetera.

Yup, and there is also a special case: the ICE!

None of my debugging equipment likes it when something issues a hard reset because the dbg-tap loses its sync signal with the target, so special care is needed to handle this specific case.

There is a procedure that works, but takes longer because it requires some manual steps and when you have a lot of things to debug (say a full test report, with many test cases), you really want to finish your task by eight paid hours per day  ;D

So, when you use an ICE, you really want to initialize things manually in a way that -1- works and -2- takes just a few milliseconds - therefore you really like moving the PC to the address where a single statement (therefore a single break-point to be armed in the ICE' memory) does all the initialization job.
That's why, in my case, I move PC to the main point where I invoke "global_init()".

It is the simplest and fastest solution possible, also because I can program it to run automatically  :D
Title: Re: Global vars initialisation at declaration or later
Post by: SiliconWizard on November 11, 2021, 06:20:18 pm
tentative definitions are a bitch anyway and very bad style IMO. Don't do that. They can only confuse anyone reading the code, for very rarely a good reason.

Not sure what you mean here.

When people want to provide a definition for a global variable in C, they often just do

Code: [Select]
int global;

in some (one and only one) translation unit. They know that the variable will be zero-initialized by default and they are happy with that. End of story.

But formally, this is a tentative definition.

OK, from how the std defines a "tentative definition", you're right. I was talking in the context of what's been said in this thread and took a shortcut. I should have said: "multiple tentative definitions of the same global variable", or even more precisely: "more than one definition of the same global variable including a possible definition with an initializer".

So, only one non-extern-specified definition of any global variable, with or without an initializer, looks acceptable to me. That's what I meant.

Title: Re: Global vars initialisation at declaration or later
Post by: SiliconWizard on November 11, 2021, 07:09:56 pm
Ah, okay.  Then the use case with extern int a; in (shared) headers, is fine, and int a[ = <initial value>]; is only done in the, wherever module it comes from.

I was a bit confused by this recently, as I had a project that apparently compiled and worked normally, with only tentative definitions in (shared) headers, in whatever flavor of compiler (and settings) comes with Cube; but stock GCC generated errors (so that'd be strict model, then).  Evidently it was compiled with whatever settings to permit the relaxed model.

Apparently, GCC's default allowed it until relatively recent versions, so it didn't require any particular setting. I had such a project that was using a third-party library with this kind of nasty stuff. Never really noticed, GCC was never complaining - until recently. With the latest GCC versions (don't remember for sure if it starts with the 10.x or 11.x series), they changed the default behavior for this, and now you get an error unless you add a specific setting to the command line.
Title: Re: Global vars initialisation at declaration or later
Post by: T3sl4co1l on November 11, 2021, 09:21:52 pm
Aha, looks like Cube uses GCC 9, and I installed 10.  So version 10 it was indeed.

Tim
Title: Re: Global vars initialisation at declaration or later
Post by: SiliconWizard on November 11, 2021, 10:40:52 pm
https://gcc.gnu.org/gcc-10/porting_to.html
Title: Re: Global vars initialisation at declaration or later
Post by: AntiProtonBoy on November 12, 2021, 01:00:50 am
One thing that has not been discussed in great detail is the issue surrounding the static initialisation order fiasco (https://en.cppreference.com/w/cpp/language/siof).

In short, if you have global variables interdependent across multiple translation units (or implementation files), then their values will be undefined.

The most reliable way to work around this issue is to construct the variable (with global semantics) on first use. Toy example:

Code: [Select]
// global.h

int universalConstant();

Code: [Select]
// global.c

int universalConstant()
{
static int value = 42;

return value;
}