Linker and Libraries Guide

Maximizing Shareability

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

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

Move Read-Only Data to Text

Data elements that are read-only should be moved into the text segment using const declarations. For example, the following character string resides in the .data section, which is part of the writable data segment.


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

In contrast, the following character string resides 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";

Reducing the data segment by moving read-only elements into the text segment is admirable. However, moving data elements that require relocations can be counterproductive. For example, examine the following array of strings.


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

A better definition might seem to be to use the following definition.


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

This definition ensures that the strings and the array of pointers to these strings are placed in a .rodata section. Unfortunately, although 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. Representing the array as:


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

ensures the array pointers are maintained in the writable data segment where they can be relocated. The array strings are maintained in the read-only text segment.


Note –

Some compilers, when generating position-independent code, can detect read-only assignments that result in runtime relocations. These compilers 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). The following example generates 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.


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

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 usually results 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 results in an overall saving in memory, as only those buffers needed by the present invocation of an application are allocated. Dynamic allocation also provides greater flexibility by enabling the buffer's size to change without affecting compatibility.