glibc 2.39 gives C bit operations a standard interface
The new stdbit.h header makes operand width and zero handling part of a common C interface. Its implementation also shows where library support and compiler support meet.
Counting zero bits looks like an operation too small to need much design. The awkward part is deciding which integer width the count describes and what happens when the input is zero. Those decisions belong in the interface, because callers need the same answer regardless of how a compiler implements the operation. The arrival of <stdbit.h> in glibc 2.39, released on 31 January, gives C programs a standard vocabulary for expressing these requirements.
C was my first language, and its small vocabulary remains part of its appeal. A named operation is useful when it removes assumptions that otherwise need reconstructing from an expression. Bit manipulation is a good example. The expression may be short while its argument conversions carry much of its meaning.
The argument width belongs to the operation
The manual shipped with glibc 2.39 describes separate functions for the standard unsigned integer types. A suffix identifies the parameter type. For example, _uc accepts unsigned char, while _ui accepts unsigned int. The unsuffixed name is a type-generic macro. This lets a caller preserve the distinction between a narrow value and the same numerical value held in a wider type.
Consider an unsigned byte containing the value one. Counting its leading zero bits should describe that byte. Widening the value first changes the question, because the wider representation has more leading zeros. The numerical value alone is insufficient to determine the answer. Code that treats bit counts as properties of values without tracking their types can therefore conceal a width assumption.
The release header makes that adjustment visible. Its generic leading-zero macro starts with the unsigned long long operation and subtracts the difference between the argument width and the wider type. Its inline implementation for an eight-bit argument similarly adjusts the result of the wider compiler operation. The standard name does not make the width issue disappear. The library implements it once so each application need not reproduce it.
This distinction also explains why an unsigned suffix on a literal matters in examples. The intended argument must have an unsigned integer type. The generic interface is useful when a variable already carries the relevant width, while a suffixed function gives an explicit parameter conversion. Choosing between them is a decision about what information the calling code should preserve.
Zero has a specified answer
The glibc manual defines the leading-zero count of zero as the argument width. Its trailing-zero count has the same rule. A bit-width query has a different purpose and returns zero for a zero argument. These answers let a program express its question directly, without imposing the same convention on every family of operations.
Here is a small example using only representable values. The expected results follow from the definitions in the release manual.
#include <assert.h>
#include <stdbit.h>
int main(void)
{
unsigned int flags = 0x28u;
assert(stdc_count_ones(flags) == 2u);
assert(stdc_trailing_zeros(flags) == 3u);
assert(stdc_bit_width(flags) == 6u);
assert(stdc_bit_floor(flags) == 32u);
assert(stdc_bit_ceil(flags) == 64u);
assert(!stdc_has_single_bit(flags));
assert(stdc_bit_width(0u) == 0u);
return 0;
}
The value has two set bits. Its least significant set bit follows three zeros, and its highest set bit requires six binary digits. Those are different queries over the same input. Naming them separately makes the connection between a requirement and its implementation easier to inspect. A bitmap cardinality calculation needs a population count, while a representation-size calculation needs the position of the highest set bit.
The first-bit functions use another convention. The manual numbers positions starting at one and returns zero when the requested bit does not exist. Consequently, a first-bit result cannot be substituted directly for a zero-based array index. This is a useful interface distinction to preserve in surrounding variable names. A position with an absence sentinel and a count of preceding zeros describe related quantities but have different meanings at the boundary.
The library still depends on compiler capabilities
Joseph Myers described the implementation in his December patch submission. The functions have inline definitions and externally linked library implementations. The patch tests both forms. It also checks result types and verifies that macro arguments are evaluated exactly once. These properties matter for an interface whose ordinary spelling is a macro, because its apparent function-call syntax does not by itself establish its behavior.
The release header selects compiler builtins when the compiler provides them. Its zero-count helpers test for zero before invoking the corresponding builtin. Narrow helpers adjust counts or convert complemented values back to the intended width. These details explain the practical benefit of putting the operation behind a library interface. A caller can ask for a count without duplicating the implementation decisions that make the result match the parameter type.
The December submission also identifies a limit. This implementation does not provide general support for unsigned __int128 or arbitrary unsigned _BitInt widths. The release manual describes the generic macro in terms of unsigned arguments with widths of 8, 16, 32 or 64 bits. A new standard header therefore does not imply that every unsigned extension accepted by a compiler works through the generic interface.
I value this kind of addition because it reduces the amount of local knowledge required to read C. The remaining limitations are concrete. A reader can check the argument type against the supported interface instead of determining what a private macro intended to support.
Rounding and range remain separate questions
The floor and ceiling operations produce powers of two. Their purpose differs from the bit-width operation, which produces a count. For the example above, six describes the number of required bits, while sixty-four describes the next power-of-two value. Confusing those results would change a capacity calculation even though both functions concern the highest set bit.
The April 2023 C working draft, N3096, defines a representability requirement for stdc_bit_ceil. If the computed result cannot fit in the return type, behavior is undefined. Portable code should keep its argument within that domain. The example deliberately uses forty, whose rounded result fits in unsigned int. It makes no claim about using an overflow result as an error signal.
A standard rounding function also cannot decide an application allocation limit. The mathematical power of two may fit in an integer while exceeding the amount of storage an application permits. That policy belongs beside the capacity requirement. The library supplies a defined calculation within its domain, and the application supplies the admissible size.
For me, the useful change is the ability to state those responsibilities more plainly. C still requires attention to types and valid inputs. With <stdbit.h>, the operation itself has a common name and documented boundary behavior, leaving application code to explain why that particular count or rounded value is needed.