Computing > Programming

C - How to doing things before main() and after exit()

(1/4) > >>

hamster_nz:
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) {
  ...
}

--- End code ---


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.

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

hamster_nz:
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[255];
     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;
}

--- End code ---

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

--- End code ---

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)

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

IanB:
Your question doesn't really make sense to me.

What is to stop you doing the following?


--- Code: ---int main()
{
    // call setup code
    my_setup_routine();

    // call main processing code
    my_main_code();

    // call cleanup code
    my_cleanup_routine();
}

--- End code ---

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?

Navigation

[0] Message Index

[#] Next page

There was an error while thanking
Thanking...
Go to full version