Sujet : Re: question about linker
De : bc (at) *nospam* freeuk.com (Bart)
Groupes : comp.lang.cDate : 02. Dec 2024, 13:24:11
Autres entêtes
Organisation : A noiseless patient Spider
Message-ID : <vik8tc$3ang9$1@dont-email.me>
References : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
User-Agent : Mozilla Thunderbird
On 02/12/2024 10:30, David Brown wrote:
On 01/12/2024 21:12, Bart wrote:
Yes, this is why a module scheme (such as the kind I use) is invaluable.
>
Agreed. C does not have a real module scheme as such. But it supports getting similar effects - you just have to be disciplined in the way you write your headers. This has the disadvantage of being less consistent than, say, Pascal or Modula 2, especially if the programmer is not disciplined. And it has the advantage in flexibility - I have a scheme that I like and that works well for the kind of code I work with, but other people prefer other schemes. It's easy to fall into the trap of "my way is the right way", especially when you make your own language and you are the only user, but there is always a balance to be sought between consistency and flexibility.
In the example above, you'd define both F and G in one place. There is no header and there are no separate declarations.
>
If another module wishes to use F, then it imports the whole module that defines F.
>
Some schemes can selectively import individual functions, but to me that's pointless micro-managing.
>
To me, it is absolutely vital that the importing unit can only see the identifiers that were explicitly exported.
Speaking for my scheme, that is exactly what happens.
(First, I should say that my programs - sets of source files that will comprise one binary - are organised into 'subprograms', each of which is a chummy collection of modules that effectively import each other. The following is about one subprogram.)
Only entities marked with 'global' are visble from other modules. But if module X exports names A, B, C, all will be visible from Y.
Further, exported names D, E, F from X will be visible from X. Imports can be circular (but subprograms are hierarchical).
What I object to in other schemes are:
* Where each of dozens of modules contains a ragtag list of imports at the top. These look untidy and need to be endlessly maintained as a fresh imported function is needed from module not yet listed, or an import needs to be deleted as references to it are dropped. (This used to be my scheme too!)
* Where functions are selectively imported from each module. FGS! Instead of a few dozen imports, now there could be hundreds of lines of imported function names to maintain. You'd have time for nothing else!
It is also absolutely vital (and this is a critical missing feature for C - and a good reason to switch to C++ even if you use no other feature of that language) that the imported identifiers be in their own namespace so that they do not conflict with identifiers in the importing unit. If the language provides a feature for importing the external identifiers directly into the current unit's namespace, then it has to allow selective import of identifiers - otherwise all concepts of scalability and modularity go out the window.
Each of my modules creates a namespace. (Also, each of my subprograms creates one namespace for all entities exported from the whole library: that requires 'export' rather than 'global'.
However that namespace is rarely used. If this module imports X which exports function F, then I can write F() instead of X.F().
I only need to use the namespace if:
* This module imports two modules that both export F so there is an ambiguity (this is reported)
* This module has its own F so it shadows any imported versions. This is not reported, but has the issue that, if a local F is freshly created, it can silently shadow the previously imported F().
In my scheme, it is not even necessary for individual modules to explicitly import each other: a simple list of modules is provided in one place, and they will automatically import each others' exported entities (which include functions, variables, types, enums, structs, named constants, and macros).
>
That sounds, frankly, utterly terrible for anyone who worked with other people.
You've never used my scheme. One significant advantage is that because all modules (and subprogram imports) are listed in the lead module (usually that's all it contains), it is very easy to build a different configuration using an alternative lead module with a different collection.
Alternatively, different modules can be commented in or out, in one place. Below is the lead module of my C compiler, what is submitted to my main compiler. No other modules contain any project info (only pcl.m, a separate subprogram/library).
The only thing I haven't yet figured out is how the compiler knows the location of an imported library which may reside elsewhere in the file system. For now this is hardcoded.
--------------------------------
project =
module cc_cli
# Global Data and Tables
module cc_decls
module cc_tables
# Lexing and Parsing
module cc_lex
module cc_parse
# Generate PCL
module cc_genpcl
module cc_blockpcl
module cc_libpcl
# General
module cc_lib
module cc_support
# Bundled headers
module cc_headers # full set of embedded std headers
# module cc_headersx # dummy module with 0 headers
!Diagnostics
module cc_show
# module cc_showdummy
!IL Backend
$sourcepath "c:/px/"
import pcl # fully loaded backend
# import pclint # interpret only
end
(As is, this builds a 336KB C compiler with multiple options. With all those alternate modules commented in instead, it builds a 178KB C interpreter.)