Global vars initialisation at declaration or later - Page 1
Forum section: electronics / general

author: ricko_uk
author_rank: Super Contributor
timestamp: November 06, 2021, 03:34:42 am
content: 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

author: floobydust
author_rank: Super Contributor
timestamp: November 06, 2021, 03:52:18 am
content: 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.

author: Psi
author_rank: Super Contributor
timestamp: November 06, 2021, 04:03:04 am
content: 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.

author: TheCalligrapher
author_rank: Regular Contributor
timestamp: November 06, 2021, 05:00:07 am
content: Quote from: ricko_uk on November 06, 2021, 03:34:42 amFor 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 toinitializevariables 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.

author: Nominal Animal
author_rank: Super Contributor
timestamp: November 06, 2021, 05:26:29 am
content: 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.Quote from: ricko_uk on November 06, 2021, 03:34:42 amOn 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.

author: SiliconWizard
author_rank: Super Contributor
timestamp: November 06, 2021, 06:42:17 pm
content: Quote from: ricko_uk on November 06, 2021, 03:34:42 amOne 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.

author: ricko_uk
author_rank: Super Contributor
timestamp: November 06, 2021, 07:15:56 pm
content: Thank you all, all valid points.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: November 06, 2021, 11:07:52 pm
content: Or the other reading, which it seems isn't intended, and is fairly trivial:Global vars declaredwithoutinitialization, are a fine way to share them between modules. Usually with anexternkeyword. (Conversely,statickeeps 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

author: ricko_uk
author_rank: Super Contributor
timestamp: November 07, 2021, 01:47:48 am
content: Thank you Tim

author: TheCalligrapher
author_rank: Regular Contributor
timestamp: November 10, 2021, 05:32:38 am
content: Quote from: T3sl4co1l on November 06, 2021, 11:07:52 pm... 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`.Quote from: T3sl4co1l on November 06, 2021, 11:07:52 pmExactly. If you rely on the aforementioned non-standard extension, then whichever instance of the shared variable has anexplicitinitializer, will be picked for linking.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: November 10, 2021, 10:04:32 am
content: Hmm, what's nonstandard about it? This seems to explain it pretty normally,https://en.cppreference.com/w/c/language/externTim

author: TheCalligrapher
author_rank: Regular Contributor
timestamp: November 10, 2021, 12:46:38 pm
content: Quote from: T3sl4co1l on November 10, 2021, 10:04:32 amUm... Not sure what you mean. The "One definition rule" section there is pretty clear about the fact that you are allowedno more than one definitionof 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 inC99 Rationale.Unix compilers historically adhered toRelaxed 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 rejectedRelaxed Ref/Def modeland decided to adhere toStrict 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 thisStrict Ref/Def modeland also borrowed some details fromInitialization model.Nevertheless, some C compilers still adhere toRelaxed Ref/Def modelas 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

author: T3sl4co1l
author_rank: Super Contributor
timestamp: November 10, 2021, 03:48:06 pm
content: 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

author: TheCalligrapher
author_rank: Regular Contributor
timestamp: November 10, 2021, 04:31:01 pm
content: Quote from: T3sl4co1l on November 10, 2021, 03:48:06 pmThat's not enough. Initializers do not matter, actually.If you have several translation units withCode:int a; /* no initializer */in them, your code is already violating the One Definition Rule.The above is atentativedefinition. However, once the translation unit ends, it will implicitly produce a regular definition, equivalent toCode:int a = 0;(seehttp://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 atentativedefinition, they can have as many of those as they want in their entire program. That's not true. Tentative definition is alocalconcept, local to each translation unit. Thanks to tentative definitions you can have thisCode:int a;int a;int a, a, a, a, a;in one translation unit. Thecompiler propershall 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. Thelinkeris supposed to complain.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: November 10, 2021, 05:26:59 pm
content: Ah, okay. Then the use case withextern int a;in (shared) headers, is fine, andint 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 useextern), which seems to be an improvement. Excellent.Tim

author: DiTBho
author_rank: Super Contributor
timestamp: November 10, 2021, 06:19:27 pm
content: In my projects, you always see something likeglobal_init();${app_name}_init(p_obj); /* object' vars initialization */

author: SiliconWizard
author_rank: Super Contributor
timestamp: November 10, 2021, 06:20:38 pm
content: 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.

author: TheCalligrapher
author_rank: Regular Contributor
timestamp: November 11, 2021, 07:15:42 am
content: Quote from: SiliconWizard on November 10, 2021, 06:20:38 pmNot sure what you mean here.When people want to provide a definition for a global variable in C, they often just doCode: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 peoplewantedto 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 actuallyusingtentative definitions in any material way is when you doCode: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.

author: DiTBho
author_rank: Super Contributor
timestamp: November 11, 2021, 11:55:53 am
content: global_init() in my case is a function that initializes all the global variables.

author: DiTBho
author_rank: Super Contributor
timestamp: November 11, 2021, 12:07:34 pm
content: Quote from: Nominal Animal on November 06, 2021, 05:26:29 amYup, 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 daySo, 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

author: SiliconWizard
author_rank: Super Contributor
timestamp: November 11, 2021, 06:20:18 pm
content: Quote from: TheCalligrapher on November 11, 2021, 07:15:42 amOK, 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.

author: SiliconWizard
author_rank: Super Contributor
timestamp: November 11, 2021, 07:09:56 pm
content: Quote from: T3sl4co1l on November 10, 2021, 05:26:59 pmApparently, 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.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: November 11, 2021, 09:21:52 pm
content: Aha, looks like Cube uses GCC 9, and I installed 10. So version 10 it was indeed.Tim

author: SiliconWizard
author_rank: Super Contributor
timestamp: November 11, 2021, 10:40:52 pm
content: https://gcc.gnu.org/gcc-10/porting_to.html

author: AntiProtonBoy
author_rank: Frequent Contributor
timestamp: November 12, 2021, 01:00:50 am
content: One thing that has not been discussed in great detail is the issue surrounding thestatic initialisation order fiasco.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:// global.hint universalConstant();Code:// global.cint universalConstant(){static int value = 42;return value;}