This document describes heuristics and rules for writing C libraries that are published in the Fuchsia SDK.
A different document will be written for C++ libraries. While C++ is almost an extension of C, and has some influence in this document, the patterns for writing C++ libraries will be quite different than for C.
Most of this document is concerned with the description of an interface in a C header. This is not a full C style guide, and has little to say about the contents of C source files. Nor is this a documentation rubric (though public interfaces should be well documented).
Some C libraries have external constraints that contradict these rules. For instance, the C standard library itself does not follow these rules. This document should still be followed where applicable.
Goals
ABI Stability
Some Fuchsia interfaces with a stable ABI will be published as C libraries. One goal of this document is to make it easy for Fuchsia developers to write and to maintain a stable ABI. Accordingly, we suggest not using certain features of the C language that have potentially surprising or complicated implications on the ABI of an interface. We also disallow nonstandard compiler extensions, since we cannot assume that third parties are using any particular compiler, with a handful of exceptions for the DDK described below.
Resource Management
Parts of this document describe best practices for resource management in C. This includes resources, Zircon handles, and any other type of resource.
Standardization
We would also like to adopt reasonably uniform standards for Fuchsia C libraries. This is especially true of naming schemes. Out parameter ordering is another example of standardization.
FFI Friendliness
Some amount of attention is paid to Foreign Function Interface (FFI) friendliness. Many non-C languages support C interfaces. The sophistication of these FFI systems varies wildly, from essentially sed to sophisticated libclang-based tooling. Some amount of consideration of FFI friendliness went into these decisions.
Language versions
C
Fuchsia C libraries are written against the C11 standard (with a small set of exceptions, such as unix signal support, that are not particularly relevant to our C library ABI). C99 compliance is not a goal.
In particular, Fuchsia C code can use the <threads.h>
and
<stdatomic.h>
headers from the C11 standard library, as well as the
_Thread_local
and alignment language features.
The thread locals should use the thread_local
spelling from
<threads.h>
, rather than the built in _Thread_local
. Similarly,
prefer alignas
and alignof
from <stdalign.h>
, rather than
_Alignas
and _Alignof
.
Note that compilers support flags that may alter the ABI of the
code. For instance, GCC has a -m96bit-long-double
flag that alters
the size of a long double. We assume that such flags are not used.
Finally, some libraries (such as Fuchsia's C standard library) in our
IDK are a mix of externally defined interfaces and Fuchsia specific
extensions. In these cases, we allow some pragmatism. For instance,
libc defines functions like thrd_get_zx_handle
and
dlopen_vmo
. These names are not strictly in accordance with the
rules below: the name of the library is not a prefix. Doing so would
make the names fit less well next to other functions like
thrd_current
and dlopen
, and so we allow the exceptions.
C++
While C++ is not an exact superset of C, we still design C libraries
to be usable from C++. Fuchsia C headers should be compatible with the
C++11, C++14, and C++17 standards. In particular, function
declarations must be extern "C"
, as described below.
C and C++ interfaces should not be mixed in one header. Instead,
create a separate cpp
subdirectory and place C++ interfaces in their
own headers there.
Library Layout and Naming
A Fuchsia C library has a name. This name determines its include path (as described in the library naming document) as well as identifiers within the library.
In this document, the library is always named tag
, and is variously
referred to as tag
or TAG
or Tag
or kTag
to reflect a
particular lexical convention. The tag
should be a single identifier
without underscores. The all-lowercase form of a tag is given by the
regular expression [a-z][a-z0-9]*
. A tag can be replaced by a shorter
version of the library name, for example zx
instead of zircon
.
The include path for a header foo.h
, as described by the library
naming document, should be lib/tag/foo.h
.
Header Layout
A single header in a C library contains a few kinds of things.
- A copyright banner
- A header guard
- A list of file inclusions
- Extern C guards
- Constant declarations
- Extern symbol declarations
- Including extern function declarations
- Static inline functions
- Macro definitions
Header Guards
Use #ifndef guards in headers. These look like:
#ifndef SOMETHING_MUMBLE_H_
#define SOMETHING_MUMBLE_H_
// code
// code
// code
#endif // SOMETHING_MUMBLE_H_
The exact form of the define is as follows:
- Take the canonical include path to the header
- Replace all ., /, and - with _
- Convert all letters to UPPERCASE
- Add a trailing _
For example, the header located in the SDK at lib/tag/object_bits.h
should have a header guard LIB_TAG_OBJECT_BITS_H_
.
Inclusions
Headers should include what they use. In particular, any public header in a library should be safe to include first in a source file.
Libraries can depend on the C standard library headers.
Some libraries may also depend on a subset of POSIX headers. Exactly which are appropriate is pending a forthcoming libc API review.
Constant Definitions
Most constants in a library will be compile-time constants, created
via a #define
. There are also read-only variables, declared via
extern const TYPE NAME;
, as it sometimes is useful to have storage
for a constant (particularly for some forms of FFI). This section
describes how to provide compile time constants in a header.
There are several types of compile time constants.
- Single integer constants
- Enumerated integer constants
- Floating point constants
Single integer constants
A single integer constants has some NAME
in a library TAG
, and its
definition looks like the following.
#define TAG_NAME EXPR
where EXPR
has one of the following forms (for a uint32_t
)
((uint32_t)23)
((uint32_t)0x23)
((uint32_t)(EXPR | EXPR | ...))
Enumerated integer constants
Given an enumerated set of integer constants named NAME
in a library
TAG
, a related set of compile-time constants has the following parts.
First, a typedef to give the type a name, a size, and a
signedness. The typedef should be of an explicitly sized integer
type. For example, if uint32_t
is used:
typedef uint32_t tag_name_t;
Each constant then has the form
#define TAG_NAME_... EXPR
where EXPR
is one of a handful of types of compile-time integer
constants (always wrapped in parentheses):
((tag_name_t)23)
((tag_name_t)0x23)
((tag_name_t)(TAG_NAME_FOO | TAG_NAME_BAR | ...))
Do not include a count of values, which is difficult to maintain as the set of constants grows.
Floating point constants
Floating point constants are similar to single integer constants,
except that a different mechanism is used to describe the type. Float
constants must end in f
or F
; double constants have no suffix;
long double constants must end in l
or L
. Hexadecimal versions of
floating point constants are allowed.
// A float constant
#define TAG_FREQUENCY_LOW 1.0f
// A double constant
#define TAG_FREQUENCY_MEDIUM 2.0
// A long double constant
#define TAG_FREQUENCY_HIGH 4.0L
Function Declarations
Function declarations should all have names beginning with tag_...
.
Function declarations should be placed in extern "C"
guards. These
are canonically provided by using the __BEGIN_CDECLS
and
__END_CDECLS
macros from compiler.h.
Function parameters
Function parameters must be named. For example,
// Disallowed: missing parameter name
zx_status_t tag_frob_vmo(zx_handle_t, size_t num_bytes);
// Allowed: all parameters named
zx_status_t tag_frob_vmo(zx_handle_t vmo, size_t num_bytes);
It should be clear which parameters are consumed and which are borrowed. Avoid interfaces in which clients may or may not own a resource after a function call. If this is infeasible, consider noting the ownership hazard in the name of the function, or one of its parameters. For example:
zx_status_t tag_frobinate_subtle(zx_handle_t foo);
zx_status_t tag_frobinate_if_frobable(zx_handle_t foo);
zx_status_t tag_try_frobinate(zx_handle_t foo);
zx_status_t tag_frobinate(zx_handle_t maybe_consumed_foo);
By convention, out parameters go last in a function's signature, and
should be named out_*
.
Variadic functions
Variadic functions should be avoided for everything except printf-like
functions. Those functions should document their format string
contract with the __PRINTFLIKE
attribute from compiler.h.
Static inline functions
Static inline functions are allowed, and are preferable to
function-like macros. Inline-only (that is, not also static
) C
functions have complicated linkage rules and few use cases.
Types
Prefer explicitly sized integer types (e.g. int32_t
) to
non-explicitly sized types (e.g. int
or unsigned long int
). An
exemption is made for int
when referring to POSIX file descriptors,
and for typedefs like size_t
from the C or POSIX headers.
When possible, pointer types mentioned in interfaces should refer to
specific types. This includes pointers to opaque structs. void*
is
acceptable for referring to raw memory, and to interfaces that pass
around opaque user cookies or contexts.
Opaque/Explicit types
Defining an opaque struct is preferable to using void*
. Opaque
structs should be declared like:
typedef struct tag_thing tag_thing_t;
Exposed structs should be declared like:
typedef struct tag_thing {
} tag_thing_t;
Reserved fields
Any reserved fields in a struct should be documented as to the purpose of the reservation.
A future version of this document will give guidance as to how to describe string parameters in C interfaces.
Anonymous types
Top-level anonymous types are not allowed. Anonymous structures and unions are allowed inside other structures, and inside function bodies, as they are then not part of the top level namespace. For instance, the following contains an allowed anonymous union.
typedef struct tag_message {
tag_message_type_t type;
union {
message_foo_t foo;
message_bar_t bar;
};
} tag_message_t;
Function typedefs
Typedefs for function types are permitted.
Functions should not overload return values with a zx_status_t
on
failure and a positive success value. Functions should not overload
return values with a zx_status_t
that contains additional values not
described in zircon/errors.h.
Status return
Prefer zx_status_t
as a return value to describe errors relating to
Zircon primitives and to I/O.
Resource Management
Libraries can traffic in several kinds of resources. Memory and Zircon handles are examples of resources common across many libraries. Libraries may also define their own resources with lifetimes to manage.
Ownership of all resources should be unambiguous. Transfer of
resources should be explicit in the name of a function. For example,
create
and take
connote a function transferring ownership.
Libraries should be memory tight. Memory allocated by a function like
tag_thing_create
should released via tag_thing_destroy
or some
such, not via free
.
Libraries should not expose global variables. Instead, provide functions to manipulate that state. Libraries with process-global state must be dynamically linked, not statically. A common pattern is to split a library into a stateless static part, containing almost all of the code, and a small dynamic library holding global state.
In particular, the errno
interface (which is a global thread-local
global) should be avoided in new code.
Linkage
The default symbol visibility in a library should be hidden. Use either an allowlist of exported symbols, or explicit visibility annotations on symbols to exported.
C libraries must not export C++ symbols.
Evolution
Deprecation
Deprecated functions should be marked with the __DEPRECATED attribute from compiler.h. They should also be commented with a description about what to do instead, and a bug tracking the deprecation.
Disallowed or Discouraged Language Features
This section describes language features that cannot or should not be used in the interfaces to Fuchsia's C libraries, and the rationales behind the decisions to disallow them.
Enums
C enums are banned. They are brittle from an ABI standpoint.
- The size of integer used to represent a constant of enum type is compiler (and compiler flag) dependent.
- The signedness of an enum is brittle, as adding a negative value to an enumeration can change the underlying type.
Bitfields
C's bitfields are banned. They are brittle from an ABI standpoint, and have a lot of nonintuitive sharp edges.
Note that this applies to the C language feature, not to an API that exposes bit flags. The C bitfield feature looks like:
typedef struct tag_some_flags {
// Four bits for the frob state.
uint8_t frob : 4;
// Two bits for the grob state.
uint8_t grob : 2;
} tag_some_flags_t;
We instead prefer exposing bit flags as compile-time integer constants.
Empty Parameter Lists
C allows for function with_empty_parameter_lists()
, which are
distinct from functions_that_take(void)
. The first means "take any
number and type of parameters", while the second means "take zero
parameters". We ban the empty parameter list for being too dangerous.
Flexible Array Members
This is the C99 feature that allows declaring an incomplete array as the last member of a struct with more than one parameter. For example:
typedef struct foo_buffer {
size_t length;
void* elements[];
} foo_buffer_t;
As an exception, DDK structures are allowed to use this pattern when referring to an external layout that fits this header-plus-payload pattern.
The similar GCC extension of declaring a 0-sized array member is similarly disallowed.
Module Maps
These are part of a Clang extension to C-like languages that attempt to solve many of the issues with header-driven compilation. While the Fuchsia toolchain team is very likely to invest in these in the future, we currently do not support them.
Compiler Extensions
These are, by definition, not portable across toolchains.
This in particular includes packed attributes or pragmas, with one exception for the DDK.
DDK structures often reflect an external layout that does not match the system ABI. For instance, it may refer to an integer field that is less aligned than required by the language. This can be expressed via compiler extensions such as pragma pack.