Shared libraries

Minimize global data

All external data symbols are visible to applications. This can make maintenance difficult. Try to reduce global data, as described below.

First, try to use automatic (stack) variables. Don't use permanent storage if automatic variables work. Using automatic variables saves static data space and reduces the number of symbols visible to application processes.

Second, see whether variables really must be external. Static symbols are not visible outside the library, so they may change addresses between library versions. Only external variables must remain constant. For further tips, see ``Use #hide and #export to limit externally visible symbols''.

Third, allocate buffers at run time instead of defining them at compile time. This reduces the size of the library's data region for all processes (saving memory). It also allows the size of the buffer to change from one release to the next without affecting compatibility. Statically allocated buffers cannot change size without affecting the addresses of other symbols and, perhaps, breaking compatibility.

Define text and global data in separate source files

Separating text from global data to prevent data symbols from moving. If new external variables are needed, they can be added at the end of the old definitions to preserve the old symbols' addresses.

Libraries let the link editor extract individual members. This works fine for relocatable files, but shared libraries have a different set of restrictions. If external variables were scattered throughout the library modules resulting in external and static data being intermixed. Changing static data, like hello in the following example, moves subsequent data symbols, even the external symbols:

   Before			Broken Successor

... ... int head = 0; int head = 0; ... ... func() func() { { ... ... p = "hello"; p = "hello, world"; ... ... } } ... ... int tail = 0; int tail = 0; ... ...

Assume the relative virtual address of head is 0 for both examples. The string literals will have the same address too, but they have different lengths. The old and new addresses of tail thus might be 12 and 20, respectively. If tail is supposed to be visible outside the library, the two versions will not be compatible.

NOTE: The compilation system sometimes defines and uses static data invisibly to the user (for example, tables for switch statements).

Adding new external variables to a shared library may change the addresses of static symbols, but this does not affect compatibility. An a.out file has no way to reference static library symbols directly, so it cannot depend on their values.

NOTE: There is a real danger of mixing in static data with exported data when building a shared library. This may not immediately cause problems but may cause incompatibilities in later versions of the shared library. If the external data modules are not first, a seemingly harmless change (such as a new string literal) can break existing a.out files. Even changing the code may not be necessary to cause a problem: the user might count on the compiler to place statics at a known default location. If the compiler is ever replaced, that default location may change and then existing a.out files will also break. By this time it may be too late to do anything about and the only alternative may be to distribute multiple versions of the same shared library.

To avoid these problems, group all exported data symbols and place them at lower addresses than static (hidden) data. The following are suggestions for locating exported data symbols at lower addresses than static data:

  1. Put the exported data symbols in a file (or files) by themselves.

  2. Do not put other external data in this file. Remember, if the #hide directive has been used, all other external data may become static.

  3. Do not initialize exported data symbols with static data. For example, initializing an exported data symbol to an unnamed string literal is a bad idea since the exported data file will now contain static data intermixed with the exported data. If there is a need to initialize the exported data symbol then do so in a way that the initializations are themselves data symbols which can be defined in another data file.

  4. Put all other external data in a separate data file. Shared library users get all library data at run time, regardless of the source file organization. Consequently, all external variables' definitions can be put in a single source file without a space penalty.

  5. Place data and text object files in the #objects list as follows:

    imported symbols definition files (remember symbols mentioned in #init and #branch directives are always external)

    exported data files

    all other data files

    all other text files

  6. Check the exported data object file by dumping the object file after it has been built. The size of all data should exactly match the size of the exported data symbols.

  7. Add new exported data to the end of the exported data file.

Initialize global data

Initialize external variables, including the pointers for imported symbols. Although this uses more disk space in the target shared library, the expansion is limited to a single file. mkshlib will give a fatal error if it finds an uninitialized external symbol.

Next topic: Using the specification file for compatibility
Previous topic: Changing existing code for the shared library

© 2003 Caldera International, Inc. All rights reserved.
SCO OpenServer Release 5.0.7 -- 11 February 2003