Linker and Libraries Guide

Maximizing Shareability

As mentioned in "The Underlying System", only a shared object's text segment is shared by all processes that use it; its data segment typically is not. Each process that uses a shared object usually generates a private memory copy of its entire data segment, as data items within the segment are written to. A goal is to reduce the data segment, either by moving data elements that will never be written to the text segment, or by removing the data items completely.

The following sections cover several mechanisms that can be used to reduce the size of the data segment.

Move Read-Only Data to Text

Any data elements that are read-only should be moved into the text segment. This can be achieved using const declarations. For example, the following character string will reside in the .data section, which is part of the writable data segment:


char * rdstr = "this is a read-only string";

whereas, the following character string will reside in the .rodata section, which is the read-only data section contained within the text segment:


const char * rdstr = "this is a read-only string";

Although reducing the data segment by moving read-only elements into the text segment is an admirable goal, moving data elements that require relocations can be counter productive. For example, given the array of strings:


char * rdstrs[] = { "this is a read-only string",
                    "this is another read-only string" };

it might at first seem that a better definition is:


const char * const rdstrs[] = { ..... };

thereby insuring that the strings and the array of pointers to these strings are placed in a .rodata section. Unfortunately the user perceives the array of addresses as read-only, these addresses must be relocated at runtime. This definition therefore results in the creation of text relocations. Represented it as:


const char * rdstrs[] = { ..... };

so that the array strings are maintained in the read-only text segment, but the array pointers are maintained in the writable data segment where they can be relocated.


Note -

Some compilers, when generating position-independent code, can detect read-only assignments that will result in runtime relocations, and will arrange for placing such items in writable segments (for example .picdata).


Collapse Multiply-Defined Data

Data can be reduced by collapsing multiply-defined data. A program with multiple occurrences of the same error messages can be better off by defining one global datum, and have all other instances reference this. For example:


const char * Errmsg = "prog: error encountered: %d";

foo()
{
    ......
    (void) fprintf(stderr, Errmsg, error);
    ......

The main candidates for this sort of data reduction are strings. String usage in a shared object can be investigated using strings(1). For example:


$ strings -10 libfoo.so.1 | sort | uniq -c | sort -rn 

will generate a sorted list of the data strings within the file libfoo.so.1. Each entry in the list is prefixed with the number of occurrences of the string.

Use Automatic Variables

Permanent storage for data items can be removed entirely if the associated functionality can be designed to use automatic (stack) variables. Any removal of permanent storage will usually result in a corresponding reduction in the number of runtime relocations required.

Allocate Buffers Dynamically

Large data buffers should usually be allocated dynamically rather than being defined using permanent storage. Often this will result in an overall saving in memory, as only those buffers needed by the present invocation of an application will be allocated. Dynamic allocation also provides greater flexibility by allowing the buffer's size to change without affecting compatibility.