C - How to doing things before main() and after exit() - Page 1
Forum section: electronics / general

author: hamster_nz
author_rank: Super Contributor
timestamp: May 14, 2021, 01:04:02 am
content: Does anybody know of a handy guide detailing when an how you can get things to run before and after main() and maybe after exit()?Things like:Code:__attribute__((constructor)) static void before_main(void) {...}Specifically I want to find out what resources are available at what stage... more specifically, when it is OK to allocate stuff on the heap, and when are the standard file handles valid.

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 14, 2021, 01:44:01 am
content: I'm not sure what you want to do exactly.By definition, on a "hosted" environment, main() is the programmer's entry point. The environment is responsible for doing anything it wants, call main(), and then do anything else it wants.You can also use C in a "freestanding" way. No supposed environment, no standard library. In this case, you're in complete control.But in the hosted case, there is no standard way of dealing with anything "before" or "after" main(). I'm not sure what you mean by before main and after main anyway? How before is before? The hosted environment will call main(), what it does before that is beyond your control, and what point would be your "before" point anyway?Since you're talking about allocation on the heap and standard files, I'm assuming you're in the "hosted environment" case.Allocation on the heap is guaranteed to work as soon as you've entered the main() function (and if there's enough memory available, of course.) Likewise, standard files handles are usable as soon as you've entered main(). I'm still not sure what you want to do.

author: hamster_nz
author_rank: Super Contributor
timestamp: May 14, 2021, 04:06:21 am
content: I found using constructors useful in a project, and am wondering what else I can do.So my project is an SDR thing I'm playing with, and here is the main function that sets up the processing pipeline and then processes the samples:Code:int main(int argc, char *argv[]) {unsigned n = 0;struct pipeline *p = pipelineNew();if(p == NULL) {fprintf(stderr,"Unable to create pipeline\n");return 0;}pipelineAdd(p, "read_wave", 1, readArgs);pipelineAdd(p, "filter", 0, NULL);pipelineAdd(p, "demodulate_fsk", 0, NULL);pipelineAdd(p, "atan2", 0, NULL);pipelineAdd(p, "interpolate", 2, interpArgs);pipelineAdd(p, "quantize", 0, NULL);pipelineAdd(p, "ccsds", 0, NULL);while(1) {uint8_t buf;size_t bits = pipelinePull(p, &buf, sizeof(buf));if(bits == 0)break;n+= bits;}pipelineDelete(p);fprintf(stderr, "%u symbols generated\n",n);return 0;}The eventual aim is to define the processing pipeline in a text file, rather than in code.I've got 10 or so processing modules that perform the different steps of processing. Each module is completely standalone, and register themselves before main() is run:Code:static struct module atan2_module = {.setup = atan2_setup,.inputType = atan2_input_type,.outputType = atan2_output_type,.pull = atan2_pull,.teardown = atan2_teardown,.name = atan2_name};__attribute__((constructor)) static void before_main(void) {pipelineRegister(&atan2_module);}Each processing module self-registers inside the "before_main()" constructor function, adds this module to the global list of available modules. This makes adding new processing modules is particularly easy - just include the .o file when you link.I fully appreciate that this is very compiler specific, but it is a handy technique. I was wondering:- what other things are out there that I am ignorant of.- is it safe to call malloc() in the constructor function (which I currently avoid)- are file handles ready to go when the constructor functions are run (it seems they are)

author: rstofer
author_rank: Super Contributor
timestamp: May 14, 2021, 04:28:05 am
content: For the older ARM7TDMI chips (and maybe the new stuff as well), there was a file crt.S that held the startup code. It did things like clear the .bss segment, initialize the .data segment by copying the values from flash and call main(). All of the interrupt vectors were defined in the file. Following the call to main was an infinite loop because main() wasn't suppose to return. It would be easy to add code before and after the call to main().I never paid much attention to where the constructors were called.The code to do startup stuff has to exist somewhere. Memory doesn't get cleared by magic and copying initialized values from flash to RAM takes code.I don't know where the code would be on the more modern ARM-M chips but the functionality still has to exist somewhere. I think it can now be written in C.

author: IanB
author_rank: Super Contributor
timestamp: May 14, 2021, 04:36:07 am
content: Your question doesn't really make sense to me.What is to stop you doing the following?Code:int main(){// call setup codemy_setup_routine();// call main processing codemy_main_code();// call cleanup codemy_cleanup_routine();}You can obviously elaborate this with error checking, exception handling and so on to catch problems. How would you propose to handle errors with your "before main" design?

author: ledtester
author_rank: Super Contributor
timestamp: May 14, 2021, 04:43:41 am
content: From all of the examples of__attribute__((constructor))I've seen it seems that you can pretty much do anything in them that you do inmain, e.g.:https://try2explore.com/questions/10021250https://www.geeksforgeeks.org/__attribute__constructor-__attribute__destructor-syntaxes-c/Also, that lack of caveats in the documentation seems to suggest that there aren't really any restrictions on standard library calls likemallocor stdio calls(link to gcc-11.1.0 docs)Another option is to usedlopen()to explicitly load in shared libraries. Then you can specify the libraries to be loaded as command line arguments/environment variables obviating the need to even recompile.If you need cross-platform shared library loading support have a look at libltdl.https://www.gnu.org/software/libtool/manual/html_node/Using-libltdl.html

author: hamster_nz
author_rank: Super Contributor
timestamp: May 14, 2021, 05:32:08 am
content: Quote from: IanB on May 14, 2021, 04:36:07 ammy_setup_routine() will need to be revisited as additional processing modules are added. Using the current method this function isn't needed as adding new modules is automagically handled by the linker & C environment.Quote1. At present it can't error if used correctly. The only thing that happens when a module is registered is that a linked list of modules is constructed:Code:static struct module *first_module;void pipelineRegister(struct module *m) {m->next_module = first_module;first_module = m;}However, should something critically fatal occur (e.g. a NULL pointer is passed in), you can still write stuff to stdout and exit the program.2. If it does error, and you decide that you don't need abort the entire program, then the failed pipeline module won't be able to be found when pipelineAdd() is called, and the issue can be trapped and then.

author: ledtester
author_rank: Super Contributor
timestamp: May 14, 2021, 05:46:35 am
content: QuoteGood question whether or not you can/should call exit() within a constructor function.Alternatively you could set a global variable to indicate an error so that main() can perform the exit.

author: AntiProtonBoy
author_rank: Frequent Contributor
timestamp: May 14, 2021, 05:55:05 am
content: Quote from: hamster_nz on May 14, 2021, 01:04:02 amSoon as execution enters into main(), everything is initialised for you, static variables, constants, etc. And you can certainly use malloc and file IO at that point.Think of main() as a "sandbox" for user implemented code. The sandbox and all its plumbing must exist before the user code can run.

author: Berni
author_rank: Super Contributor
timestamp: May 14, 2021, 06:17:06 am
content: What you are trying to do is a pretty common pattern in programming.It is used in modular systems that support any sort of plugins, be it drivers, modules in a OS, modding APIs in games...etc. The module defines various "stages" that the master calls upon at the appropriate time. So you might have:- Init- Pre-Start- Start- Tick- Pre-Stop- StopThese stages are simply functions that are made visible to the master. This can be a C++ class with appropriately names functions, it could be a C struct that holds function pointers to the functions that handle each stage, it could be some fancy object in C# or Java or something that registers itself in a array on creation etc..Then once its time to actually start the master will look at the table of modules and call first PreStart for each module, then call Start for each module, then as its running it might call Tick every 10ms, then once its time to shut down it might call PreStop in each module and finally Stop for each module.Having multiple stages for start and stop might sound redundant, but it allows one module to set up some data or service in the PreStart that other modules might use as part of the Start function. This prevents the need to call certain modules in a certain order. There could be even more, like PreStart1 Prestart2 PreStart3 PreStartCleanup... etc if the modules are particularly complex and need a lot of interaction before starting.For your particular implementation in C. I would suggest first manually calling the ModuleName_Init() function for each module at the start of main() this init function would add its service calls to arrays of pointers, each for the appropriate stage such as "ServiceTick = ModuleName_Tick;" Then you just use a for loop to call every function pointer inside ServiceStart[] then ServiceTick[] a bunch of time then ServiceStop[] at the end.

author: Keith956
author_rank: Regular Contributor
timestamp: May 14, 2021, 06:25:16 am
content: Sounds to me like you want an app that is extendable using plugins. Have a look at:http://www.cplusplus.com/articles/48TbqMoL/The basic idea is you create DLL's (or shared libraries in the *nix world) that have well-defined interfaces. Then your app looks for them in some predetermined location, loads them and makes them available as extensions to your app.

author: hamster_nz
author_rank: Super Contributor
timestamp: May 14, 2021, 06:26:37 am
content: I might not have made it clear that this already works, and works really well.I am just exploring what other similar things can be done, and where such things are documented.

author: IanB
author_rank: Super Contributor
timestamp: May 14, 2021, 06:29:12 am
content: Quote from: hamster_nz on May 14, 2021, 06:26:37 amThese things are not generally part of the language standard, but rather are specific to particular vendors and implementations. So they will be documented by the vendor of the tools that you are using.You should understand that what you are doing is absolutely not "C", and therefore is not portable. If you move to a different platform everything you are doing will likely stop working.Any time you see something with two underscores in it, such as "__attribute__", that is an indication that you are dealing with a local extension, and have taken a detour off the main road.

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 15, 2021, 05:02:04 am
content: Quote from: hamster_nz on May 14, 2021, 01:04:02 amThesefunction attributesare GNU extensions provided by GCC, and implemented by the linker in Linux and some BSD variants (but not Mac OS); they are not part of C, nor are they supported in all operating systems.In Linux, functions markedconstructorhave their addresses listed in .init_array section, and functions markeddestructorin .fini_array section. The startup/linker code executes the constructors just before calling main() and destructors when returning from main() and when callingexit()(but not when calling_exit(). This means that such constructor/destructor functions can bestatic, without an exported symbol at all.(Thesectionvariable attribute can be used to collect elements (of the same type, properly aligned and sized) from different compilation units into a single array. This is used in e.g. the Linux kernel to collect module license information, and can be used to make it trivial for a system utility to collect its command-line options and other structures automagically based on what parts were enabled at compile time.)When an executable is loaded in Linux, the linker can even call a function to resolve a symbol. This is done via theifuncfunction attribute, and is based on the ELF file standard optional features. (This means it is not technically a Linux extension, but an ELF extension. The constructor/destructor attributes are similarly based on optional features in the ELF standard). This is extremely useful if you want your binaries to include optimized functions for different hardware architectures, as this incurs no extra overhead at all compared to a normal, non-inline function.When an executable or library is loaded in Linux, these get called recursively, but I'm not exactly sure of the ordering; normally (without priority overrides, i.e. a priority argument to theconstructorfunction attribute), I do believe "everything just works" because the depended-on library constructors get called before the dependee's. Thus, for the executable itself, everything that is available at the beginning of main() should be available in the constructors (assuming no priority has been set for the constructor); for dynamic libraries, everything the dynamic library is linked against, should be available when its default-priority constructors are called.This also means that if your application does not need to unload plugins at run time, it is basically trivial to implement plugins in Linux due to the support of these ELF features. Basically, your application provides a registration function that plugins can use to register the new features the plugin implements, and the plugin just needs to have a (static) constructor function that registers the features when the library is loaded. Trick is, if you write the code properly (so that it is safe to call these registration functions beforemain(), as long as the standard C library has been initialized), you don't even need to add dlopen() code to the application (although I definitely would; I'd probably scan an user-specific plugin directory for symlinks to the currently active plugins), as the user can preload the desired plugins by setting the LD_PRELOAD environment variable to point to the plugin file paths (separated either by spaces or by colons : ).Although there are more "tricks" described in the above pages related to GCC and ELF binaries and libraries, these are the ones I routinely use.

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 15, 2021, 05:08:46 am
content: (Before anyone asks, similar features are available in the Linux kernel because its binary is also in ELF format. Obviously, it uses its own internal dynamic linker, but that one implements these features as well. That linker is actually pretty crafty, as it examines various headers in kernel module ELF headers, and marks the kernel tainted etc. depending on those (including the license).)

author: westfw
author_rank: Super Contributor
timestamp: May 18, 2021, 09:27:36 am
content: Avr-gcc has .initN and .finiN sections. (N goes up to about 9?)This seems to be entirely implemented within the crt files and linker scripts, so it would probably be implementable for any gcc target.https://www.nongnu.org/avr-libc/user-manual/mem_sections.html

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 18, 2021, 12:20:52 pm
content: Quote from: westfw on May 18, 2021, 09:27:36 amThey are. You can even do this in your own programs.For example,foo.h, to show how to export string pointers to particular sections:Code:#ifndef FOO_H#define FOO_H#define _MERGE2(a, b) a ## b#define MERGE2(a, b) _MERGE2(a, b)#define FOO1_STRING(string) static const char *const MERGE2(magic_, __LINE__) __attribute__((used, section ("FOO1"))) = string#define FOO2_STRING(string) static const char *const MERGE2(magic_, __LINE__) __attribute__((used, section ("FOO2"))) = string#endif /* FOO_H */Instead of strings, those could be structures instead. (Note that when defined this way, the string contents are in the standard .rodata section; only thestatic const char *const magic_linenumpointers are put into the special section.)An example program,example.c:Code:#include <stdlib.h>#include <stdio.h>#include "foo.h"extern const char *const __start_FOO1;extern const char *const __stop_FOO1;extern const char *const __start_FOO2;extern const char *const __stop_FOO2;int main(void){printf("We have the following %zu foo1 strings:\n", (size_t)(&__stop_FOO1 - &__start_FOO1));for (const char *const *p = &__start_FOO1; p < &__stop_FOO1; p++) {printf("\t\"%s\"\n", *p);}printf("We have the following %zu foo2 strings:\n", (size_t)(&__stop_FOO2 - &__start_FOO2));for (const char *const *p = &__start_FOO2; p < &__stop_FOO2; p++) {printf("\t\"%s\"\n", *p);}return EXIT_SUCCESS;}/* We do need at least one entry per section, to have the __start_/__stop_ symbols. */FOO1_STRING("example.c: foo1 string");FOO2_STRING("example.c: foo2_string");Compile usinggcc -Wall -Wextra -O2 -c example.cgcc -Wall -Wextra -O2 example.o -o test0and run./test0which will outputWe have the following 1 foo1 strings:"example.c: foo1 string"We have the following 1 foo2 strings:"example.c: foo2_string"Now, let's add a couple of additional object files.foo1.c:Code:#include "foo.h"FOO1_STRING("foo1.c: Hello, world!");andfoo2.c:Code:#include "foo.h"FOO2_STRING("foo2.c has two strings");FOO2_STRING("That and this one");Let's compile them into object files,gcc -Wall -Wextra -O2 -c foo1.cgcc -Wall -Wextra -O2 -c foo2.cand compile three additional versions of the binary:gcc -Wall -Wextra -O2 example.o foo1.o -o test1gcc -Wall -Wextra -O2 example.o foo2.o -o test2gcc -Wall -Wextra -O2 example.o foo1.o foo2.o -o test3Running these new binaries (./test1,./test2, and./test3) shows that the strings have magically appeared to the pointer array!Yes, this is exactly the section stuff I described earlier. If we examine the binaries using sayobjdump -h test3, we can see the FOO1 and FOO2 sections among the section headers. GCC places the data we wanted into custom sections (of type CONTENTS, ALLOC, LOAD, DATA; i.e. available just like initialized variables aka .data section – this can be overridden using the section attribute, but you usually do not want to do that), and the linker provides us with the__start_and__stop_symbols for these custom sections.The order of the entries in the section array is determined by the link object order. Above,test3hasexample.o foo1.o foo2.oso the objects they declare appear in the array in that order.This works even with freestanding C (bare metal, without the standard C library at all), as it relies on the ELF file format, and the linker.An example of where this could be very useful, is a modular menu for an AVR (or ARM) gadget, compiled using the GNU toolchain. If you have a structure that describes a main menu entry or page, with buttons that can switch between previous and next ones (and optionally other buttons that switch between sub-entries or sub-pages, or fields in the entry) – in any case, with the main menu entries as an array; for example, as a pointer to a more complex menu structure –, you can use this to automagically enable menus for features based on whether they're compiled in or not, without any preprocessor ifdeffery or compile-time configuration per se.For larger applications, using an array (of pointers) to describe supported command-line options is an useful use case. Typically, my structures contain the short and the long options, usage description, parameter type/count, and a callback function parsing the option if used. The-hor--helpoption then scans through the section array, displaying the usage for each option this binary supports. That way, a new feature can be cleanly implemented in a separate file – like foo1.c and foo2.c above –, without modifications to the existing code.

author: Doctorandus_P
author_rank: Super Contributor
timestamp: May 23, 2021, 03:51:38 am
content: Static variables are initialized before main() runs, and if you use C++ then the constructors of static classes also run before main().Apart from that, if you really want to know what is going on, and you should if you want to go into details as this, you get into linker scripts and startup code and those are compiler dependent.Some use assembly code you can modify. Using some special compiler constructs is also common.

author: Jan Audio
author_rank: Frequent Contributor
timestamp: June 29, 2021, 03:58:22 pm
content: I have seen a topic on microchip forum there is actually something before main in the XC compiler, for initializing.If you wanto be the best and know everything i understand you want it, else i see no reason.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 30, 2021, 12:42:59 am
content: Quote from: Jan Audio on June 29, 2021, 03:58:22 pmIt is nothing special, really; it justfeelsodd the first time you encounter it. I can explain it all in a single post, if you want.And there aretwocompletely separate things before main(), not just one. One is that for dynamic executables, the run time linker will execute things it sees in the executable. That, of coursse, only occurs if you have a run time linker; and you only have those on full-blown OSes like Linux and Windows, and not on embedded devices.The second is the one you alluded to; for various reasons, it is often called C Runtime, orcrt; and it is part of the set of base libraries –– usually avr-libc or newlib with AVR and ARM targets, respectively; I'm not sure which XC (Microchip compiler) you refer to, but I wouldn't know for sure which base libraries it uses anyway.If you look at say avr-libc, compiled with either gcc or clang for various AVR targets, the magic happens inavr-libc/crt1/gcrt1.S. The compiler provides a default linker script (see sayhardware/tools/avr/avr/lib/ldscripts/avr5.xin Arduino sources to see the default linker file for ATmega32u4 – the suffix describes the criteria (a set of compiler and linker options), not any format, by the way), which describes how the various ELF sections are arranged and combined to put together to get an uploadable binary.Internally, avr-libc uses 21 ELF sections for the critical parts:.vectorsfor the array of entries the hardware uses when an interrupt occurs, the first one of which is __init, the one the hardware uses when it starts up;.init0through.init8for the machine code run beforemain()is called,.init9doing that call or jump; and.fini9through.fini1for stuff that needs to be done aftermain()returns orexit()(or its variants) is called, with.fini0being responsible for the forever loop or system restart.The exact same "trick" as I showed in my example above, is used by the linker script to merge the ten .init sections into a single consecutive chunk of machine code that finally jumps into or calls C main() function. Similarly, the .fini sections are merged into a single consecutive chunk of machine code that gets called if main() returns, or one of the exit() functions is called.So, there is not much to it at all. If you use Arduino, go to Preferences, and make sure you have Show verbose output during: compilation checked, and I suggest also the Compiler warnings: all. Then, when you Verify/Compile a sketch, you can see the temporary directory and ELF object file names and paths it uses in the output. If you look up the.ino.elfone, you can use theavr-objdumputility included in Arduino to examine the ELF object file. Use the-dflag to get the AVR disassembly; other flags show other useful stuff. In the disassembly, find symbol __vectors. On AVRs, the interrupt vectors are actually jump instructions, and the very first one is taken at startup. In Arduino, for AVRs, it is usually to __ctors_end, but may vary. An example one I just checked contains 36 instructions before the call to main():__ctors_end(writes 0 to I/O port 0x3F, 0x0A to port 0x3E, 0xFF to port 0x3D),__do_copy_data(copies initialized variable and object data from Flash to RAM),__do_clear_bss+__do_clear_bss_loop+__do_clear_bss_start(clearing RAM corresponding to uninitialized/zero initialized variables), and__do_global_ctorsthat calls all ELF constructor functions (via __tablejump2__ helper function, which is just six additional instructions that first loads the address to jump to from 2*Z (Z being the register pair r30:r31 on AVRs), then jumps to that address; return address is in another register pair). Those ELF constructor functions are the ones marked with__attribute__((constructor)); and for C++ constructors, one that takes no parameters and uses the explicit object address to call the C++ function using the C++ calling convention, will be created automatically by the compiler.I'm not the best, and although I want to know everything, it's only because I'm curious to a fault. The reason anyone else might want to know the details, is becausesometimesthey may come in handy.For example, if I found I need to get a C function executed in Arduino before its secret main() starts, in an AVR Arduino sketch, then by knowing the above, I'd *know* that__attribute__((constructor)) static void myfunc(void) { /* do stuff before main */ }would do exactly that. I happen to know that not only does it work with ALL Arduino cores, but it also works in normal hosted environments in Linux and BSDs the exact same way.Oh, and because XC are derived from either GCC or LLVM/Clang, which both support all of the above as long as ELF object files are used, all of the above applies to XC too.For an example XC use case, perhaps you have a largeish table in RAM that you don't want to waste Flash for. What you do, is leave it uninitialized, but create the above constructor function to initialize it. Done! For all intents and purposes, by the time main() starts, it will be initialized correctly. If you do need it initialized before other constructor functions get executed (perhaps a C++ object constructor uses that table?), it gets a bit more complicated (you need to check if setting a priority suffices, or whether you need to add a linker script detail, to ensure the order) – say, maybe five minutes of tinkering, and half an hour of testing the results are correct.Granted, this information is a bit esoteric and not needed by everyone, but there definitely are use cases and reasons why one might care to know this.