# Linking C Libraries

### Linking C Libraries

Importing a C header lets Zig understand a C API. Linking gives the final program the actual compiled code.

These are separate steps.

A header file says what exists:

```c
// mathlib.h

int add(int a, int b);
```

A source file or library provides the implementation:

```c
// mathlib.c

#include "mathlib.h"

int add(int a, int b) {
    return a + b;
}
```

In Zig, this import only reads the declaration:

```zig
const c = @cImport({
    @cInclude("mathlib.h");
});
```

It does not include the body of `add`. If the final executable cannot find the compiled implementation, the linker fails.

#### The Build Has Two Jobs

When using C from Zig, your build usually needs to do two things.

First, it must tell Zig where the C headers are:

```text
include path -> where .h files live
```

Second, it must tell Zig where the C implementation is:

```text
source file, object file, static library, or dynamic library
```

The header lets the compiler check your call.

The implementation lets the linker build the final executable.

#### Linking a C Source File

For small libraries, you may compile the C source file directly with your Zig program.

Project layout:

```text
project/
  build.zig
  src/
    main.zig
    mathlib.c
    mathlib.h
```

C header:

```c
// src/mathlib.h

#ifndef MATHLIB_H
#define MATHLIB_H

int add(int a, int b);

#endif
```

C source:

```c
// src/mathlib.c

#include "mathlib.h"

int add(int a, int b) {
    return a + b;
}
```

Zig code:

```zig
// src/main.zig

const std = @import("std");

const c = @cImport({
    @cInclude("mathlib.h");
});

pub fn main() void {
    const result = c.add(10, 20);
    std.debug.print("result = {}\n", .{result});
}
```

In `build.zig`, you add the include path and the C source file.

A simplified example:

```zig
const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "app",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });

    exe.addIncludePath(b.path("src"));
    exe.addCSourceFile(.{
        .file = b.path("src/mathlib.c"),
        .flags = &.{},
    });

    b.installArtifact(exe);
}
```

Now the build knows both parts:

```text
src/mathlib.h -> available to @cImport
src/mathlib.c -> compiled and linked into the executable
```

#### Linking a Static Library

A static library is usually a file such as:

```text
libmathlib.a
```

On Windows, it may be:

```text
mathlib.lib
```

A static library is copied into the final executable at link time. The result is usually more self-contained.

Project layout:

```text
project/
  build.zig
  src/
    main.zig
  include/
    mathlib.h
  lib/
    libmathlib.a
```

Zig imports the header:

```zig
const c = @cImport({
    @cInclude("mathlib.h");
});
```

The build must provide the include directory and the library search path:

```zig
exe.addIncludePath(b.path("include"));
exe.addLibraryPath(b.path("lib"));
exe.linkSystemLibrary("mathlib");
```

The library name usually drops the `lib` prefix and `.a` suffix.

So this file:

```text
libmathlib.a
```

is linked as:

```zig
exe.linkSystemLibrary("mathlib");
```

This naming convention comes from Unix-style linkers.

#### Linking a Dynamic Library

A dynamic library is loaded at runtime.

Common names:

| Platform | Dynamic library extension |
|---|---|
| Linux | `.so` |
| macOS | `.dylib` |
| Windows | `.dll` |

If you link dynamically, the executable depends on that library being available when the program runs.

For example, if your program links to SQLite dynamically, the compiled executable may still need `libsqlite3.so`, `libsqlite3.dylib`, or `sqlite3.dll` on the target machine.

The Zig build code may look similar:

```zig
exe.addIncludePath(b.path("include"));
exe.addLibraryPath(b.path("lib"));
exe.linkSystemLibrary("sqlite3");
```

But the runtime behavior differs.

Static linking copies code into the executable.

Dynamic linking records a dependency on an external library.

#### Static vs Dynamic Linking

| Linking style | What happens | Main advantage | Main cost |
|---|---|---|---|
| Static | Library code is copied into the executable | Easier deployment | Larger executable |
| Dynamic | Executable loads library at runtime | Smaller executable, shared library updates | Runtime dependency |

For command-line tools, static linking can be convenient.

For desktop applications and system libraries, dynamic linking is common.

For plugins or libraries that must match system versions, dynamic linking may be required.

There is no universal best choice. It depends on deployment, licensing, platform rules, and library behavior.

#### Linking the C Standard Library

Some C libraries depend on libc.

In Zig build files, you may need:

```zig
exe.linkLibC();
```

This tells Zig to link the C standard library.

You usually need this when your Zig executable calls C code that uses libc functions such as `malloc`, `free`, `printf`, `fopen`, or `strlen`.

Example:

```zig
exe.addCSourceFile(.{
    .file = b.path("src/mathlib.c"),
    .flags = &.{},
});
exe.linkLibC();
```

For very small C files that do not use libc, this may not be necessary. But many real C libraries need it.

#### Header Found, Link Failed

A very common situation is:

```text
The header was found.
The code compiled.
The linker failed.
```

That usually means Zig knows the function declaration, but it cannot find the implementation.

Example error shape:

```text
undefined symbol: add
```

or:

```text
undefined reference to `add`
```

This means the final link step could not find compiled code for `add`.

The fix is usually one of these:

```text
Add the C source file.
Add the object file.
Add the static library.
Add the dynamic library.
Add the correct library search path.
Use the correct library name.
```

Do not try to fix this by changing `@cImport`. The import already did its job. The problem is linking.

#### Header Not Found

Another common error is:

```text
'mathlib.h' file not found
```

That is different.

It means the compiler cannot find the header file during `@cImport`.

The fix is to add the include path:

```zig
exe.addIncludePath(b.path("include"));
```

Think of the two errors separately.

| Error | Meaning | Fix |
|---|---|---|
| Header not found | Compiler cannot find `.h` file | Add include path |
| Undefined symbol | Linker cannot find implementation | Link source file or library |

This distinction saves a lot of time.

#### Linking System Libraries

Some libraries are installed on the system.

For example, on many Unix-like systems, you may link math functions from `libm`.

In C, this often looks like:

```bash
cc main.c -lm
```

In Zig build code, it may look like:

```zig
exe.linkSystemLibrary("m");
```

For pthreads:

```zig
exe.linkSystemLibrary("pthread");
```

For SQLite:

```zig
exe.linkSystemLibrary("sqlite3");
```

This asks the system linker to find a library with that name.

This only works if the library is installed and visible to the linker.

#### Library Search Paths

If a library is not in a standard system location, you need to tell the build where to look.

```zig
exe.addLibraryPath(b.path("vendor/sqlite/lib"));
exe.linkSystemLibrary("sqlite3");
```

For headers:

```zig
exe.addIncludePath(b.path("vendor/sqlite/include"));
```

These two paths solve different problems.

```text
include path -> compile-time headers
library path -> link-time library files
```

A project that vendors a C dependency usually needs both.

#### Adding C Compiler Flags

Some C files require compile flags.

For example:

```zig
exe.addCSourceFile(.{
    .file = b.path("src/library.c"),
    .flags = &.{
        "-std=c11",
        "-Wall",
    },
});
```

Flags are passed to the C compiler when compiling that C file.

You may need flags for:

```text
C standard version
warning settings
feature macros
include behavior
optimization options
platform-specific configuration
```

Keep these flags close to the C source file in the build file. That makes the build easier to inspect.

#### Defining C Macros from the Build

Some C libraries use macros for configuration.

You can define macros inside `@cImport` with `@cDefine`, but you may also need to define them when compiling the C source.

For example:

```zig
exe.addCSourceFile(.{
    .file = b.path("src/library.c"),
    .flags = &.{
        "-DMY_FEATURE=1",
    },
});
```

This affects the C source compilation.

If the header also depends on the macro, define it in the import too:

```zig
const c = @cImport({
    @cDefine("MY_FEATURE", "1");
    @cInclude("library.h");
});
```

The C source and the Zig import should agree on configuration. If they use different macros, you can get mismatched declarations and implementations.

#### Object Files

Sometimes you already have a compiled object file:

```text
mathlib.o
```

An object file is compiled code that has not yet been linked into the final executable.

You can link object files into your program.

Conceptually:

```zig
exe.addObjectFile(b.path("lib/mathlib.o"));
```

Use this when a dependency ships object files instead of source files or static libraries.

For beginners, C source files and static libraries are more common.

#### Platform Differences

Linking is platform-sensitive.

Linux, macOS, and Windows use different library formats, default paths, system libraries, and runtime lookup rules.

Examples:

| Platform | Static library | Dynamic library | Common linker behavior |
|---|---|---|---|
| Linux | `.a` | `.so` | Uses system library paths and rpath rules |
| macOS | `.a` | `.dylib` | Uses frameworks and install names |
| Windows | `.lib` | `.dll` | Uses import libraries and DLL search paths |

This is why Zig’s build system is useful. You can express platform-specific rules in Zig code instead of maintaining many separate shell scripts.

Example shape:

```zig
if (target.result.os.tag == .windows) {
    exe.linkSystemLibrary("ws2_32");
} else {
    exe.linkSystemLibrary("m");
}
```

The exact library names depend on the API you use.

#### C++ Is Different

This chapter is about C, not C++.

C++ linking is more complicated because of name mangling, constructors, destructors, templates, exceptions, RTTI, and the C++ standard library.

Zig can interact with C++ in some cases, but C interop is much simpler and more direct.

When possible, expose a C API from C++ code:

```cpp
extern "C" int add(int a, int b);
```

Then call that C-compatible API from Zig.

This keeps the boundary stable.

#### A Clean Mental Model

When linking C libraries, keep four things separate:

```text
Header files
C source files
Library files
Runtime library loading
```

Header files are for compilation.

C source files are compiled into object code.

Library files provide object code to the linker.

Dynamic libraries may also be needed at runtime.

If you keep these layers separate, most errors become easier to diagnose.

#### What to Remember

`@cImport` imports declarations.

Linking provides implementations.

Use `addIncludePath` for headers.

Use `addCSourceFile` for C source files.

Use `addLibraryPath` and `linkSystemLibrary` for libraries.

Use `linkLibC` when your C code depends on libc.

A “header not found” error is an include-path problem.

An “undefined symbol” error is a linking problem.

Static libraries make deployment simpler but produce larger binaries.

Dynamic libraries keep binaries smaller but add runtime dependencies.

