Re: [cc65] linker cfg files

Date view Thread view Subject view

From: Ullrich von Bassewitz (uz_at_musoftware.de)
Date: 2003-05-01 23:53:36


On Thu, May 01, 2003 at 02:30:40PM -0700, Shawn Jefferson wrote:

> Ullrich: What things did you think could be put into a constructor?  I saw a bunch of stuff around changing the margins and cursor state, as well as a "conio init".  There is also some stdio definitions but these probably have to be there (?)

A constructor is used to run initialization code that is needed when one
specific module is used. The docs for the assembler (ca65) do contain a more
detailed explanation together with an example.

The problem constructors (and destructors) solve is the following: If part of
your library needs initialization (say conio, or file i/o) this initialization
must only be called when the specific parts of the library are actually used.
If a C program doesn't use file i/o, there's no need to initialize it. In
fact, doing so would include code and data that is not needed. If you have
libraries (like the one cc65 comes with) that contain many optional parts,
initialization for all these parts can eat up quite some memory, and if you're
calling the initialization code from the startup module, *all* programs will
have the overhead, even if they don't use anything from the library.

> How does a constructor work?  Does it get put into the constructor/destructor table when a module containing a constructor function is linked to your program, and then crt0.s calls a function that jsrs to each of the constructors?

A constructor is nothing more than a command that tells the assembler to
export a name in a special way. The linker is able to build a table containing
all the marked identifiers from all modules linked into an executable. If you
look into the standard linker configs, you will find something like

    FEATURES {
        CONDES: segment = RODATA,
                type = constructor,
                label = __CONSTRUCTOR_TABLE__,
                count = __CONSTRUCTOR_COUNT__;
        CONDES: segment = RODATA,
                type = destructor,
                label = __DESTRUCTOR_TABLE__,
                count = __DESTRUCTOR_COUNT__;
        # condes functions with type 2 are called in the interrupt
        CONDES: segment = RODATA,
                type = 2,
                label = __IRQFUNC_TABLE__,
                count = __IRQFUNC_COUNT__;
    }

This tells the linker to build a table with the name __CONSTRUCTOR_TABLE__ for
all contructors, another one with the name __DESTRUCTOR_TABLE__ with all
destructors, and a third one with the name __IRQFUNC_TABLE__ for all
identifiers marked as .CONDES type 2 (you can see from that example that the
use is not limited to constructors and destructors).

The startup file contains library calls that run the constructors/destructors
in the table:

	jsr	initlib                 ; Call module constructors
        jsr    	donelib                 ; Call module destructors

This means that the startup code doesn't need to know, which modules need
initialization. It just calls some subroutines that in turn call all functions
from a table, which is built by the linker. This does also mean that adding
another subroutine has a very low overhead: Just two bytes in the table.

One thing to care about is that the tables do only contain functions from
modules linked to the executable. So if there is no reference to a module, it
won't get linked to the executable, and it's constructor is never called. So a
constructor has to go into a module that is shared by all modules that
implement a common API.

Regards


        Uz


-- 
Ullrich von Bassewitz                                  uz_at_musoftware.de
----------------------------------------------------------------------
To unsubscribe from the list send mail to majordomo_at_musoftware.de with
the string "unsubscribe cc65" in the body(!) of the mail.


Date view Thread view Subject view

This archive was generated by hypermail 2.1.3 : 2003-05-01 23:53:59 CEST