Proven C Book한국어 GitHub

56 Handling name collisions — from prefixes to namespace

What to know first

chapter 54, Many files — splitting and linking · external and internal linkage
chapter 55, The world of names · four name spaces and three axes

Looking back

Chapter 55 said C has only four name spaces and that you cannot make a new one. What happens, then, when two libraries that know nothing of each other each export a function called init?

A. If both have external linkage the linker refuses with a multiple definition — and that is the lucky case. The unlucky one is worse. If one of them sits inside a static library, the linker takes the first one it finds and moves on in silence. Compiling and linking both succeed, and the wrong function is called.

So the answer to this problem is not “fix it when it collides” but “make a collision impossible in the first place.” This chapter is those methods.

The need for this chapter, and its context

If chapter 55 was “what collides”, this is “so what do you do”. Splitting principle from prescription into two chapters is deliberate: the prescriptions — prefixes, visibility, shortening names — become superstition without the principle. What large projects actually chose is examined here alongside.

By the end of this chapter

Four weapons against name collisions, in order — do not export, the prefix convention, symbol visibility, and cutting down the names themselves. What large projects actually chose comes with them. Finally, how C++ solved this spot with namespace, and what to watch for when the two languages are mixed.

The questions this chapter answers

  1. Is multiple definition actually the welcome error?
  2. How do I check what I am actually exporting?
  3. Should C then bring name spaces into the language?

56.1 When and where collisions go off

Spellings collide in three places, and each is harder to diagnose than the last.

WhenWhat tells youHow easy to handle
At compile timedeclared twice in one scope — the compiler refuses at onceEasy. It is visible right there
At link timemultiple definition of 'init'Moderate. The message names the files
Silentlynothing tells youHard. The wrong function runs and you see only symptoms

Table 57.1

The third is why this chapter exists. Three places where silent collisions arise.

Q. Is multiple definition actually the welcome error?

A. Yes. It is an audible collision. What is genuinely dangerous is the linker picking one side without a word.

So the direction of the discipline is not “let us fix collisions well” but “let us make sure a collision is heard.” Cut the exported names to a minimum and (a) the chance of colliding drops and (b) when one happens the linker makes a noise. All four weapons below point that way.

56.2 The first weapon — do not export (static)

The surest defence is not putting the name outside at all. Add static to a file-scope declaration and it has internal linkage (chapter 54): outside that translation unit it does not exist.

The discipline reduces to one line — make static the default and release only what goes in the header. There is a bonus: knowing “this name cannot be called from another file”, the compiler can optimise more aggressively (inlining, removing unused functions).

Counter-example. Leaving something global that was never meant for the header

/* util.c */
int helper(int x) {}         /* not in any header, yet external */

Nobody calls it, and still it occupies a name in the linker’s yard. The day another file happens to make the same name, the problem surfaces. The cost of writing static is one word.

56.3 The second weapon — the prefix convention

For names that must be exported, the fence is built by hand. It is the universal practice of C libraries, and the larger the project the fewer the exceptions.

ProjectPrefixHow far it goes
SQLitesqlite3_functions, types, constants — even the version number is in the prefix
libcurlcurl_, CURLlowercase for functions, capitals for constants and types
zlibz, inflate/deflateold enough that the prefix is short — which is why it sometimes collides
OpenSSLSSL_, EVP_, X509_a different prefix per module
GLib, GTKg_, gtk_, G_, GTK_types in camel case, as GtkWidget
SDLSDL_functions, types, constants and macros alike
The Linux kernelthe subsystem’s namekmalloc, vfs_, sock_ — the layer as the prefix

Table 57.2

Four design rules can be read out of it.

  1. Short, but unique. zlib’s z is convenient for being short and collides just as easily. Three or four characters is a safe choice today.
  2. Put it on types and macros too. A prefix that covers only functions is half a fence — chapter 55 showed that type names and enumeration constants live in the same yard.
  3. Split the layers by case. Combine the prefix with the habit of lowercase for functions and capitals for macros and constants, and a name alone reveals what it is.
  4. Write it down. One line in the conventions document saying “our prefix is this” is cheaper than pointing it out a hundred times in review.

In practice. The legacy of the days before prefixes

The standard library is itself a museum of unprefixed names — open, read, write, time, index, link. In the 1970s a program used a handful of libraries, so it was not a problem.

Today’s common accident is the result. Make a function called read or time in your own code and you hide the standard one, and other code calling it quietly goes elsewhere. That is what chapter 55′s table of reserved names is for, and “assume the short, common words are already taken” is practice’s first rule.

56.4 The third weapon — symbol visibility

A prefix is a convention people keep, so it leaks. There is one more layer that lets the build enforce it — the symbol visibility of a shared library.

PlatformHiding by defaultMarking what is exported
ELF (Linux, BSD)-fvisibility=hidden__attribute__((visibility("default")))
macOS-fvisibility=hiddenthe same attribute, or a list file of exported symbols
Windowsalready hidden by default__declspec(dllexport) or a .def file

Table 57.3

The practice is to wrap it in one macro.

#if defined(_WIN32)
#  define MYLIB_API __declspec(dllexport)
#elif defined(__GNUC__)
#  define MYLIB_API __attribute__((visibility("default")))
#else
#  define MYLIB_API
#endif

MYLIB_API int mylib_open(const char *path);

Three effects: collisions grow rarer, loading gets faster (fewer symbols to resolve), and the boundary between the internal functions you may change and the public ones you may not is written into the code.

Q. How do I check what I am actually exporting?

A. Count them. Pull the defined global symbols out of the object file or library.

nm -g --defined-only mylib.o      # an object file
nm -D --defined-only libmy.so     # a shared library (dynamic symbols)
objdump -T libmy.so               # the same job with another tool

Measure this chapter’s listing that way and the exported names are just two, main and textbuf — the inner functions are all static and absent from the list.

One good habit: dump the list of public symbols to a file and compare it in the build. If a new name leaks out unintended, the build says so. It is rung 3 of chapter 12′s ladder (let the build tell you) applied to names.

56.5 The fourth weapon — cut down the names themselves

Better than building a good fence is having few names to put outside. There is a pattern in which a module, instead of exporting twenty functions, exports a single struct holding function pointers.

examples/ch56/prefix.c

/* 「모듈 하나 = 외부 심볼 하나」 — 접두어 규약과 함수 포인터 표.
   C 에는 사용자가 만드는 이름 공간이 없으니, 내보내는 이름을 줄이는 것이
   가장 확실한 방어다. */
#include <stdio.h>
#include <string.h>

/* ── 안쪽 구현은 전부 내부 연결(static) — 바깥 마당에 나가지 않는다 ── */
static int  buf_len;
static char buf[64];

static void impl_reset(void)                 { buf_len = 0; buf[0] = '\0'; }
static bool impl_push(const char *s)
{
    size_t n = strlen(s);
    if ((size_t)buf_len + n + 1 > sizeof buf) return false;   /* 잘림은 실패다(43장) */
    memcpy(buf + buf_len, s, n + 1);
    buf_len += (int)n;
    return true;
}
static const char *impl_text(void)           { return buf; }

/* ── 밖에 내보내는 것은 이것 하나 ──
   표(vtable) 하나에 접두어를 붙여 두면, 이 번역 단위가 링커에 내미는
   이름은 `textbuf` 단 하나다. 나머지 철자는 다른 파일과 부딪힐 수 없다. */
struct textbuf_api {
    void        (*reset)(void);
    bool        (*push)(const char *);
    const char *(*text)(void);
};

const struct textbuf_api textbuf = {
    .reset = impl_reset,
    .push  = impl_push,
    .text  = impl_text,
};

/* ── 쓰는 쪽 ── */
int main(void)
{
    textbuf.reset();
    puts("[this module exposes exactly one external name: `textbuf`]");
    printf("  push(\"hello \") -> %s\n", textbuf.push("hello ") ? "ok" : "failed");
    printf("  push(\"world\")  -> %s\n", textbuf.push("world")  ? "ok" : "failed");
    printf("  text()         -> \"%s\"\n", textbuf.text());

    char big[80];
    memset(big, 'A', sizeof big - 1);
    big[sizeof big - 1] = '\0';
    printf("  pushing a long string -> %s (truncation does not count as success)\n",
           textbuf.push(big) ? "ok" : "failed");

    puts("\n[the prefix convention - what large projects do]");
    puts("  sqlite3_ / curl_ / SSL_ / g_ / SDL_ ... the same prefix goes on types and macros");
    puts("  too. A prefix is a name space that C lacks, supplied by hand.");
    return 0;
}

Output

[this module exposes exactly one external name: `textbuf`]
  push("hello ") -> ok
  push("world")  -> ok
  text()         -> "hello world"
  pushing a long string -> failed (truncation does not count as success)

[the prefix convention - what large projects do]
  sqlite3_ / curl_ / SSL_ / g_ / SDL_ ... the same prefix goes on types and macros
  too. A prefix is a name space that C lacks, supplied by hand.

The only name this translation unit hands the linker is textbuf (the nm measurement above). The calling side writes textbuf.push(...) with a dot — and the bonus of the pattern is that it reads like a language that has name spaces.

The price is worth stating plainly.

What you gainWhat you pay
external names collapse to onecalls go through a function pointer — inlining gets hard
the implementation can be swapped wholeone level of indirect call slower
the caller reads like a name spacea debugger no longer shows the callee at a glance

Table 57.4

So the pattern belongs at boundaries — plugins, swappable back ends, test doubles. Not in an inner loop where speed matters.

56.6 How C++ solved this spot

C++ put “the user digs a yard” into the language. Here is as much as a C programmer needs, and accurately.

56.6.1 The syntax

namespace app {
    int  parse(const char *);
    namespace detail { int helper(int); }   // nested
}
namespace app::io { void flush(); }         // the C++17 shorthand

int x = app::parse("42");
int y = app::detail::helper(1);

The yard’s name goes in front of the name. It does the same job as C’s prefix convention; what differs is that the language enforces it and the tools understand it.

56.6.2 The anonymous namespace — the equivalent of C’s static

namespace { int hidden(int x) { return x; } }   // this translation unit only

Measuring shows the character of it.

DeclarationSymbol kind (nm)Meaning
static int st(int);t — localinternal linkage, as in C
hidden in an anonymous namespacet — localthe same effect; the name prints as (anonymous namespace)::hidden
an ordinary int use(int);T — globalexternal linkage

Table 57.5

The two have the same effect, but C++ prefers the anonymous namespace — unlike static it can be applied to types as well, and those can be passed as template arguments.

56.6.3 using namespace — the convenience and its price

using namespace std; tears down the yard’s fence on the spot. Handy in a short example; the conventions of practice mostly run like this.

ConventionGround
Never in a headerthe fence comes down for every file that includes it
Narrow even in a source fileone name at a time, as using std::string;
Inside a functionthe effect is confined to that function
swap is the exception — it has its own idiombecause of ADL, below

Table 57.6

56.6.4 ADL — looking in the yard of the argument

C++ has argument-dependent lookup: the yard where the argument’s type lives is searched as well, automatically.

namespace app { struct Buf {}; void print(const Buf &); }
app::Buf b;
print(b);        // app:: was not written, yet app::print is called — ADL

Convenient, and a surprising place too. Which function gets called depends on the type of the argument, so in code with many overloads a person struggles to follow it. C has no such lookup — one name is one function. Worth remembering as a spot where C’s simplicity pays.

56.6.5 Name mangling and extern "C"

C++ supports overloading, so the same spelling with different argument types is a different function. For that, the name the linker sees must carry type information — that is mangling.

SourceThe name the linker sees (GCC, measured)
namespace app { int f(int); }_ZN3app1fEi
namespace app { double f(double); }_ZN3app1fEd
extern "C" int c_f(int);c_f

Table 57.7

Take _ZN3app1fEi apart and the yard name app (three characters), the function name f (one) and the argument type i are all in there. The name space is carved into the symbol.

So mixing C and C++ means saying “name this function by C’s rules”, and that is extern "C". The canonical pattern for a C header:

#ifdef __cplusplus
extern "C" {
#endif

int mylib_open(const char *path);

#ifdef __cplusplus
}
#endif

Platform note. Where a C header breaks in C++ alone

Some headers are fine in C and will not compile in C++. The cause is nearly always one thing — a C++ keyword used as a name.

int register_thing(int class, int new);   /* C: fine. C++: an error */

Measured, the C compiler says nothing and the C++ compiler refuses with expected primary-expression before 'int'.

So when writing a header that may also be used from C++, courtesy is to keep C++‘s reserved words (class, new, delete, template, this, namespace, try, catch, operator, private, public, virtual and the rest) out of parameter names. Append an underscore (class_) or pick another word.

Words that became keywords in C23 — bool, true, false — also turn up as variable names in old C headers. The same family of problem.

Q. Should C then bring name spaces into the language?

A. It has been proposed several times and folded each time for the same reason — the ABI and existing code. Bringing in name spaces means the yard must be carved into the symbol (the mangling above), and that splits forty years of libraries and linking conventions. C survives today as “the common denominator any language can call” precisely because its symbol names are simple (chapter 96).

In other words this is not a deficiency but a trade. C gave up convenience in names and gained simplicity in linking. That simplicity is why Python, Java and Rust all speak C’s ABI.

So this chapter’s weapons are not stopgaps. They match C’s design, and large projects have got along on them for decades.

56.7 Good habits — a summary

HabitWhy
Make static the defaulta name never exported cannot collide
Settle a prefix and write it downa fence built by people needs agreement to stand
Prefix types, macros and enumeration constants tooall three share one yard (ch. 55)
Avoid short, common wordsread, time, index already have owners
Let the build enforce visibilitytools plug what conventions leak
Dump the public symbol list and compare itthe moment one leaks, the build says so
Keep C++ keywords out of headersyou cannot know everywhere they will be used
Turn on -Wshadowshadowing is not caught by the default warnings (ch. 55)

Table 57.8

We know how to govern names. But chapter 55 went past the remark that “macros ignore all four name spaces” — meaning a layer that knows nothing of C replaces names. The next chapter opens that layer head on, and follows the formal steps by which source code becomes a program.