1. Revision History
1.1. Revision 4 - September 7th, 2024
-
Drastically improve the paper’s reasoning and examples in § 4 Design.
-
Add several real-life code examples from C standard and platform libraries in the § 8 Appendix.
-
Specifically address the idea that glibc and other libraries which have special names must keep linker names under a specific name and thus breaks this feature / breaks
in § 2.4 dlsym, GetProcAddress, and More.dlsym -
Specifically address the way composite types and other redeclarations of the types are done; refined the § 7 Wording.
1.2. Revision 3 - December 10th, 2023
-
Clean up a few terribly written sentences in the explanation and prose.
-
Rewrote large parts of the wording based on feedback from Joseph Myers.
-
Completely clean up redeclarations and other interactions (redeclarations of existing functions do not matter to this feature, as explained in § 4.4.1 Standard Library Redeclaration).
1.3. Revision 2 - June 17th, 2022
-
Provide implementation of Transparent Aliases, available at this Clang fork.
-
Provide a proof of concept using the above implementation, which works on Windows, Mac, and Linux machines where the modified Clang compiler can be deployed.
-
Tweak redeclaration rules for Aliases, to solve Standard Library and other library redeclarations in the face of Aliases.
-
Add wording for functionality for variables, since all implementations support variables in the prior art.
-
Mark release for C2y/C3a.
1.4. Revision 1 - September 15th, 2021
-
Adjust motivation / explanation of
and how it relates to this proposal.__attribute__ (( alias ( …)))
1.5. Revision 0 - May 15th, 2021
-
Initial release. ✨
2. Introduction & Motivation
After at least 3 papers were burned through attempting to solve the intmax_t problem, a number of issues were unearthed with each individual solution ([N2465], [N2498], [N2425], [N2525]). Whether it was having to specifically lift the ban that §7.1.4 places on macros for standard library functions, or having to break the promise that
can keep expanding to fit larger integer types, the Committee and the community at large had a problem providing this functionality.
Thankfully, progress is being made. With Robert Seacord’s "Specific-width length modifier" paper was approved for C23 ([N2680]), we solved one of the primary issues faced with
improvements, which was that there was no way to print out a integral expression with a width greater than
. Seacord’s addition to the C standard also prevented a security issue that commonly came from printing incorrectly sized integers as well: there’s a direct correlation between the bits of the supported types and the bits of the given in the formatting string now, without having to worry about the type beyond a
check. This solved 1 of the 2 core problems.
2.1. Remaining Core Problem - Typedefs, ABI, and Macros
Library functions in a "very vanilla" implementation of a C Standard Library (e.g., simply a sequence of function declarations/definitions of the exact form given in the C Standard) have a strong tie between the name of the function (e.g.,
) and the symbol present in the final, compiled binary (e.g.,
). This symbol is tied to a specific numeric type (e.g.,
), which creates a strong relationship between the way the function is called (register usage, etc.) and more. Upgrading that type breaks old binaries that still call the old symbol; for example,
being handed a
instead of a
as an old application anticipates can result in the wrong registers being used, or worse. Thusly, because the Standard Library is bound by these rules and because implementations rely on functions with
-based types in them to resolve to a very specific symbol, we cannot upgrade any of the
s (e.g., what
is) or change anything about the functions (e.g., change
’s declaration in any way consequential fashion).
Furthermore, macros cannot be used to "smooth" over the "real function call" because §7.1.4 specifically states that a user of the standard library has the right to deploy macro-suppressing techniques (e.g.,
) to call a library function (unless the call is designated to be a macro). This also includes preventing their existence as a whole with
: every call after that
directive to
must work and compile according to the Standard. While this guarantees users that they can always get a function pointer or a "real function name" from a given C Standard library function name, it also refuses implementations the ability to provide backwards compatibility shims using the only Standards-applicable tool in C and C++.
2.2. Liaison Issue - Stopping C++ Improvements
This is both a C Standard Library issue and a C++ Standard Library issue. Not only is it impossible to change the C Standard Library, but because of these restrictions and because the C Standard Library is included by-reference into C++, we cannot make any of the necessary changes to solve this problem in C++. This elevates the level of this problem to a liaison issue that must be fixed if we are to make forward progress in both C and C++.
2.3. Standardizing Existing Practice
While the C Standard Committee struggles with this issue, many other libraries that have binary interfaces communicated through shared or dynamically linked libraries have solved this problem. MSVC uses a complex versioning and symbol resolution scheme with its DLLs, which we will not (and could not) properly standardize here. But, other implementations have been using implementation-defined aliasing techniques that effectively change the symbol used in the final binary that is different from the "normal" symbol that would be produced by a given function declaration.
These techniques, expanded upon in the design section as to why we chose the syntax we did for this proposal, have existed for at least 15 years in the forms discussed before, and longer with linker-specific techniques. Existing practice has allowed it for both functions and variables for as long as can be tracked using this code, so we make aim to make both functions (declarations and definitions) and variables work.
2.4. dlsym
, GetProcAddress
, and More
There is, unfortunately, no guarantee that asking for e.g.
or similar raw name from the C standard library will actually directly translate to having such a definition findable in a program with an appropriately loaded standard library with implementation-defined functionality such as
or
. Oftentimes, users need to actually specifically ask for certain function names, including decorating them with
and
(to match some musl-libc functions) or similar for glibc and otherwise. How long these symbols stick around is a matter of implementation quality, though sometimes such changes result in very difficult-to-change Application Binary Interfaces.
From NetBSD, in large text in a heading:
Programs that use dlsym(3), such as C foreign function interfaces in dynamic languages like Python, need to know that if they want the legacy 32-bit
function, they must use the symbol
time () , and if they want the modern 64-bit time() function, they must use the symbol
time .
__time50
It is clear that C implementations do not require a 1:1 mapping between C standard library names and actual symbols on the machine.
2.5. C Issue - Prevented Evolution
Not fixing this issue also comes with a grave problem without considering C++ at all. We have no way of seamlessly upgrading our libraries without forcing end-users to consider ABI breakage inherit in changes type definitions or having library authors jump through implementation-specific and frightening hoops for creating (transparent) layers of indirection between a function call and the final binary. This means large swaths of C’s standard library, due to §7.1.4, are entirely static and non-upgradeable, even if we write functions that use type definitions that can change.
This is, by itself, a completely untenable situation that hampers the growth of C. If we cannot even change type definitions due to constraints such as linkage names from old code without needing a computer-splitting architectural change (e.g., the change from
"32-bit" architectures to
"64-bit" architectures that allowed for
to change), with what hope could be possibly have in getting C to evolve? How can we have it meet current hardware specifications and software needs? Users have been raging on about the lack of an
in C, or a maximum integer type, and some implementers and platform users have stated:
I am unreasonably angry about this, because the
situation has kept me from enabling completely-working first-class
intmax_t support out of clang for a decade. In that decade, I personally would have used 128b literals more times in personal and professional projects than the entire world has used
int128 to beneficial effect, ever, in total.
intmax_t
At the surface of this issue and as illustrated by the many failed — and one successful — papers for
is that we need a better way for type definitions to be used for interfaces in the C standard. Underlying it is a wound that has begun to fester in the presence of not having a reason to invent wildly new architectures that necessitate fundamentally recompiling the world. Our inability to present a stable interface for users in a separable and Standards-Compliant way from the binary representation of a function that we cherish so deeply is becoming an increasing liability. If every function (the fundamental unit of doing work) essentially becomes impossible to change in any way, shape, or form, then what we are curating is not a living and extensible programming language but a dying system that is unequivocally doomed to general failure and eventual replacement.
3. Existing Practice
To solve this problem, we pull from heavily deployed existing practice across all toolchains, standard libraries, embedded and server toolchains, and many more commercial and non-commercial compilers. It is, perhaps, one of the most widely deployed existing practices in the world insofar as C is concerned.
3.1. __attribute__ (( alias (...)))
It is not a coincidence that, in the initial example in § 4 Design, we are using
prefixes for the 2
function calls. Tying a function to a name it does not normally "mangle" to in the linker is a common implementation technique among more advanced C Standard Library Implementations, such as musl-libc and glibc. It is also a common technique deployed in many allocators to override symbols found in downstream binary artifacts, albeit this proposal does not cover the "weak symbol" portion of the alias techniques deployed by these libraries since that is sometimes limited to specific link-time configurations, binary artifact distributions, and platform architecture.
Particularly, this proposal is focusing on the existing GCC-style attribute and a Clang-style attribute. The GCC attribute ([gcc-attribute]) provides the behavioral inspiration (but not exactly) for this proposal in its requirements, by effectively allowing for an existing function declaration to have its address made identical to the function it is aliasing:
void __real_function ( void ) { /* real work here... */ ; } void function_decl ( void ) __attribute__ (( alias ( "__real_function" )));
This code will set up
to contain an additional (potentially external) entry point for the address of
. It is common implementation practice amongst compilers that are GCC-compatible and that focus on binary size reduction and macro-less, transparent, zero-cost indirection. It has the small caveat that it requires the target of the attribute to be fully defined before this happens, so it can definitively insert the address of the new entry point. For example, here is documentation from the Keil’s armcc compiler ([keil-attribute]):
static int oldname ( int x , int y ) { return x + y ; } static int newname ( int x , int y ) __attribute__ (( alias ( "oldname" ))); int caller ( int x , int y ) { return oldname ( x , y ) + newname ( x , y ); }
This code compiles to:
AREA || . text || , CODE , READONLY , ALIGN = 2 newname ; Alternate entry point oldname PROC MOV r2 , r0 ADD r0 , r2 , r1 BX lr ENDP caller PROC PUSH { r4 , r5 , lr } MOV r3 , r0 MOV r4 , r1 MOV r1 , r4 MOV r0 , r3 BL oldname MOV r5 , r0 MOV r1 , r4 MOV r0 , r3 BL oldname ADD r0 , r0 , r5 POP { r4 , r5 , pc } ENDP
There are many compilers which implement exactly this behavior with exactly this GCC-extension syntax such as Oracle’s C Compiler, the Intel C Compiler, the Tiny C Compiler, and Clang ([oracle-attribute], [intel-attribute]).
Intermediate object files may still carry the name "
", as well as an "alternate" name for the chosen functionality. This gets up fairly close to the desired behavior but not quite: we want to create an entity that, ultimately, has no linkage and is specified in such a way that it will almost always disappear in both intermediate objects and final artifacts, wherever possible, while still providing a level of compile-time indirection. In this way this feature departs from how
in GCC’s attribute syntax works, but we find this departure to be worth the effort.
In order to clean away the minor issue of always creating a secondary external declaration that produces a "second name" in final artifacts with
and similar, we then turn to another existing practice that does such renaming transparently, but does not require a second function declaration to map to a first one and does not always produce a secondary external name:
labels and
definitions.
3.2. asm ( "..." )
/__asm ( "..." )
/__asm__ ( "..." )
The second and equally as prominent existing practice for this behavior is what is known as "assembly labels". Clang also features this
-style attribute, where the function’s name is "mangled" to exactly the name given ([clang-asm]). GCC has this behavior as well ([gcc-attribute]). This is how NetBSD and other BSDs have been solving their Application Binary Interface woes for nearly 30 years now. To borrow the example from NetBSD, they had an issue with their old, 32-bit time function being a 32-bit number. To solve this problem, they used 2 source files and 1 header file to export 2 differently-named symbols, but present the same declaration in the frontend for users:
// old, fixed declarations long time ( long * ptime ) { // no label: exports "time" symbol /* old source here... */ }
#include <time.h>time_t time ( time_t * ptime ) __asm ( "time50" ) { // label: exports "time50" symbol /* source here... */ }
typedef long long time_t ; time_t time ( time_t * ptime ) __asm ( "time50" );
This sequence of header and source files accomplishes the goal precisely of:
-
maintaining backwards binary compatibility with old binaries that expect a
function to exist that works with the registers associated with taking atime
argument and returning a 32-bitlong
argument;long -
presenting the updated, user-facing, new
function that will not overwrite the other symbol (as it’s name in the "assembly" has been changed to "time
");time50 -
allows a seamless upgrade experience in the right direction.
Notice how this has allowed NetBSD to go from
to
without issue: in general, this works extraordinarily well for functions that are paired with a typ definition. Users can be seamlessly upgrading by changing the type definition and/or structure while updating a function to have a newer assembly label in lock-step. Code that compiles more recently gets the new version with the new binary name, while old code that is not recompiled or changed continues to have its structures, layouts, expected stack/register usage preserved. This provides a way to upgrade users compiling new code without breaking binary users: in effect, this gets around the Application Binary Interface Break (ABI Break) problem that exists for many libraries in C.
3.3. #pragma
s
Microsoft Visual C uses a (slightly more complex) stateful pragma mechanisms and external compiler markup ([msvc-exports]). MSVC’s
-based approach can be coordinated entirely inside of the compiler, or it can rely on
commands and files given to a linker. Oracle’s
([oracle-pragma]) is another similar extension that performs the equivalent of a find-and-replace for a given symbol name with the new symbol name. These are effectively assembly labels but done through blanket find-and-replace and similar implementation strategies. We will not expand upon it much further than that, as it is not much more interesting than "assembly labels through
".
They are harder to standardize because each mechanism relies too heavily on instructing the platform’s linker and the details of binary artifacts to get the job done. It is hard to work with these as there is very little meaningful semantic constraints that can be placed on their designs that are enforceable within the boundaries of the Abstract Machine for standard C, and the experience when working with either of these directly is subpar; there can be no linker errors at all until an executable that needs a specific function is loaded, and only then will an error be tossed by the application loader. This is not friendly design for library writers or application developers.
3.4. Other Existing Practice: IBM Compilers
IBM has also stated that they have a similar extension in some of their non-public offerings, which create a compile-time link between two identifiers that does not perform any run-time or link-time replacement or changes.
4. Design
Our goal with this feature is to create an identifier with no linkage (therefore costing no binary space even with a naïve implementation) that can point to any variable or function, following the widespread § 3 Existing Practice. From the motivation and analysis above, we need the following properties:
-
It must be a concrete name (identifier), not a macro.
-
It must be able to decay to a function pointer when referenced plainly by name, like a normal function declaration, and that value must be usable (unlike a function macro).
-
It should not require producing a symbol on non-interpreter implementations of C for a simple, straightforward implementation of the feature.
-
It should allow for an implementation to upgrade or change the arguments or return type of a concrete symbol without requiring a detectable binary break when used in the same way that the existing practice has been used for C implementations.
To fulfill these requirements, we propose the a new transparent-alias construct that, in general, would be used like such:
extern long long __glibc_imaxabs228 ( long long ); extern __int128_t __glibc_imaxabs229 ( __int128_t ); /* ... */ #if __GNU_LIBC <= 228 _Alias imaxabs = __glibc_imaxabs228 ; #else _Alias imaxabs = __glibc_imaxabs229 ; #endif /* ... */ int main () { intmax_t x = imaxabs ( -50 ); return ( int ) x ; }
It is composed of the
keyword, followed by an identifier, the
token, and then another identifier. The identifier on the right hand side must be either a previously declared transparent-alias or name a function declaration. Below, we explore the merits of this design and its origins.
4.1. Transparency - "Type Definitions, but for Functions"
We call this transparent because it is, effectively, unobservable from the position of a library consumer, that this mechanism has been deployed. The following code snippet illustrates the properties associated with Transparent Function Aliases:
#include <assert.h>int other_func ( double d , int i ) { return ( int )( d + i ) + 1 ; } int real_func ( double d , int i ) { return ( int )( d + i ); } _Alias alias_func = real_func ; /* The below is a Constraint Violation. You cannot redeclare */ /* a function alias with an incompatible signature. */ //void alias_func(void); /* Constraint violation: */ /* the redeclaration of an ordinary identifier into a different entity. */ //void alias_func(double d, int i); /* No Constraint Violation: redeclaration of an alias pointing /* to the same declaration is fine. */ _Alias alias_func = real_func ; /* Constraint Violation: redeclaration of an alias pointing */ /* to a different declaration than the first one is not */ /* allowed. */ //_Alias alias_func = other_func; int main ([[ maybe_unused ]] int argc , [[ maybe_unused ]] char * argv []) { assert ( & alias_func == & real_func ); // no constraint Violation typedef int ( real_func_t )( double , int ); real_func_t * real_func_ptr = alias_func ; // decays to function pointer of real_func real_func_t * real_func_ptr2 = & alias_func ; // function pointer to real_func [[ maybe_unused ]] int is_3 = alias_func ( 2.0 , 1 ); // invokes real_func directly [[ maybe_unused ]] int is_4 = real_func_ptr ( 3.0 , 1 ); // invokes real_func [[ maybe_unused ]] int is_5 = real_func_ptr2 ( 3.0 , 2 ); // invokes real_func assert ( is_3 == 3 ); // no constraint violation assert ( is_4 == 4 ); // no constraint violation assert ( is_5 == 5 ); // no constraint violation assert ( real_func_ptr == & real_func ); // no constraint violation assert ( real_func_ptr == & alias_func ); // no constraint violation assert ( real_func_ptr2 == & real_func ); // no constraint violation assert ( real_func_ptr2 == & alias_func ); // no constraint violation }
The notable properties are:
-
always "forwards" its calls toalias_func
without needing the end-user to call "real_func
" directly;real_func -
can be used in constant expressions, just like normal functions with their address taken;alias_func -
, like any other function call, cannot be redeclared as a normal function declaration of any form;alias_func -
cannot be re-aliased to a different function call after the first;alias_func -
any function pointer obtained from
is identical to a function pointer obtainedreal_func
; and,alias_func -
andreal_func
have identical addresses.alias_func
In short,
works like any other function declaration would, but is not allowed to have its own function definition. It is simply an "alias" to an existing function at the language level. Given these properties, no implementation would need to emit a whole new function address for the given type; any binary-producing implementation would produce the same code whether the function was called through
or
. It gets around the requirement of not being able to define C functions as macros, while maintaining all the desirable properties of a real C function declaration.
It also serves as a layer of indirection to the "real function", which means function alias definitions and type definitions can be upgraded with one another while improving backwards compatibility.
4.2. Modeled after both asm (...)
and __attribute__ (( alias (...)))
Transparent aliases have the practical effects of an
label applied to a function or variable, but borrows some of the type checking and useful behavior that is behind
. Notably, the attribute alias approach requires that a previous, in-language definition exists. If that definition does not exist, the program has a constraint violation:
#if 0 extern inline int foo () { return 1; } #endif int bar () __attribute__ (( alias ( "foo" ))); // <source>:5:27: error: alias must point to a defined variable or function // int bar () __attribute__((alias("foo"))); // ^ int main () { return bar (); }
’s entity-checking constraints are more suitable for Standard C, since we do not want to specify this in terms of effects on a binary artifact or "symbols".
is also not suitable for the user-facing design of this because it requires someone to know the precise mangling of a function call. While this can be helpful in exotic situations, we are okay with letting the wide plethora of existing practice cover the more exotic cases while providing for a strongly-typed, compiler-checked version that simply provides an invisible layer of indirection that is similar to the design of
. Therefore, we gear our design to make no imposition on what may or may not happen to the exported symbols.
Crucially important, though, is that this behaves like
in that it does not actually export a symbol name for the Transparent Alias that is declared. This allows that name to be reused for binary artifacts in a more precise manner than was previously possible under these sorts of situations without implementation extensions. It only ties one Compilation Stage 5 entity to another Compilation Stage 5 entity in what is typically known as an implementation’s "front end". Whether or not final binary artifacts do the right thing is still up to an implementation (and always will be, because the Standard cannot specify such). This gives us proper semantics without undue burden on either specification or implementation.
In short, as with
s, we want to focus on creating an entity that has no linkage but for functions/variables (objects).
4.3. Why not an [[ attribute ]]
?
At this point in time, one might wonder why we do not propose an attribute or similar for the task here. After all, almost all prior art that does not use compiler options and/or the preprocessor uses an attribute-like or literal
syntax. Our reasons are 2-fold.
4.3.1. Standard Attributes may be ignored
The ability to ignore an attribute and still have a conforming program is disastrous for this feature. It reduces portability of libraries that want to defend against binary breakages. Note that this is, effectively, the situation we are in now: compilers effectively ruin any implementation-defined extension by simply refusing to support that extension or coming up with one of their own. Somewhat ironically, those same vendors will attend C Committee meetings and complain about binary breakages. We then do not change anything related to that feature area, due to the potential of binary breakages.
The cycle continues and will continue ad nauseum until Standard C provides a common-ground solution.
4.3.2. There is no such thing as "mangled name" or "symbol name" in the Standard
Any attempt at producing normative text for a "symbol name" construct is incredibly fraught with peril and danger. Vendors deserve to have implementation freedom with respect to their what their implementation produces (or not). Solving this problem must be done without needing to draft specification for what a "binary artifact" or similar may be and how an attribute or attribute-like construct could affect it. If this feature relies primarily on non-normative encouragement or notes to provide ABI protection, then it is not fit for purpose.
Therefore, we realize that the best way to achieve this is to effectively allow for a transparent aliasing technique for functions, similar to type definitions. It must be in the language and it must be Standard, otherwise we can never upgrade any of our type definitions without waiting for an enormous architectural break (like the 32-bit to 64-bit transition).
4.4. Backwards-Compatibility with "Vanilla" C Standard Libraries
One of the driving goals behind this proposal is the ability to allow "vanilla" C Standard Library Implementations to use Standards-only techniques to provide the functions for their end-user. Let us consider an implementation — named
, that maybe produces a
binary — that, up until today, has been shipping a
function declaration for the last 2 decades. Using this feature, we can provide an _entirely backwards compatible_, binary-preserving upgraded implementation of
that decides to change it’s
function declarations. For example, it can use 2 translation units
and
and one header,
, to produce a conforming Standard Library implementation that is also backwards-compatible with binaries that continue to link against
:
:
#include <inttypes.h>__int128_t __imaxabs_vanilla_v2 ( __int128_t __value ) { if ( __value < 0 ) return - __value ; return __value ; }
:
extern inline long long imaxabs ( long long __value ) { if ( __value < 0 ) return - __value ; return __value ; }
:
/* upgraded from long long in v2 */ typedef __int128_t intmax_t ; extern intmax_t __imaxabs_vanilla_v2 ( intmax_t ); _Alias imaxabs = __imaxabs_vanilla_v2 ;
As long as
is linked with the final binary artifact
, the presumed mangled symbol
will always be there. Meanwhile, the "standard"
will have the normal
symbol that is tied in a transparent way to the "Version 2" of the vanilla implementation,
. This produces a perfectly backwards compatible interface for the previous users of
. It allows typedefs to be seamlessly upgraded, without breaking already-compiled end user code. Newly compiled code will directly reference the v2 functions with no performance loss or startup switching, getting an upgraded
. Older programs compiled with the old
continue to reference old symbols left by compatibility translation units in the code.
4.4.1. Standard Library Redeclaration
One of the bigger problems that comes with this design space is that the C Standard Library specifically allows for a user to redeclare an existing standard library symbol:
Provided that a library function can be declared without reference to any type defined in a header, it is also permissible to declare the function and use it without including its associated header.
That means the following declaration in this translation unit is legal:
extern char * strcpy ( char * restrict s1 , const char * restrict s2 ); int main () { return 0 ; }
This is a restriction that is special to C. Thankfully, we are not particularly concerned about the ability to upgrade this function: users who are declaring Standard Library functions without including the header like this are doing this strictly as experts. They have a strong expectation of what symbol they are getting from their distribution. Transparent aliases are meant to be used for functions which rely on type definitions or structures which may change, prompting the need to provide updated global variables and updated functions without breaking old binaries. Some famous C standard library examples of this include (but are not limited to):
-
(struct timespec
, and other functions);timespec_get -
(time_t
,time
,mktime
, and more functions);difftime -
(intmax_t
,imaxabs
, and similar functions);strtoimax -
and similar functions to handle changes tostrtoul
and% b
that might be breaking;% B -
and so on, and so forth.
These are types which are controlled in conjunction by the standard library and are known to change on 32-bit and 64-bit systems as well as transition based on locale and other compile-time settings. A single DLL can be distributed on a system and accommodate a variety of preprocessor-based switches or other compile-time information and allow an information to accommodate and/or upgrade a given system’s symbol table (or DLL/SO’s symbol table) without breaking old, original code.
Therefore, we do not do anything to support or inhibit such declarations. Implementations looking to keep such declarations working from older versions of code should consider leaving those old symbols within their binary artifacts (system tables, shared/static libraries, etc.) to continue supporting such a use case; this proposal is not going to address it or the myriad of other issues around this (such as strong/weak symbols and other attributes/aliasing issues).
4.5. The _Alias a = b
syntax
The primary reason the syntax
is chosen here is because we want to provide an in-language construct for doing this without requiring that the end-user completely re-declare the function they want to alias. For example, an alternative syntax was considered as follows:
extern void b ( int w , double x , struct yy * y , struct zz * z ); void a ( int w , double x , struct yy * y , struct zz * z ) = b ;
This gave the "function" and "redeclaration" feeling to
, but it required that all arguments essentially be reproduced exactly (or risk constraint violations). This introduces fault-intolerance, where function arguments could change and cause breakage in downstream code. This could result in a lot of unnecessary maintenance work for end-users and package maintainers alike responding to library developers changing their type definitions, or similar. It might end up tying future developer’s hands not for binary stability reasons, but for source breakage reasons.
Now that
is in C23, we can write the above as the following:
extern void b ( int w , double x , struct yy * y , struct zz * z ); typeof ( b ) a = b ;
This is much more palatable, but violates the rule of "don’t repeat yourself". Repeating ourselves leaves the potential, still, for slight misspellings between what is inside of
and what is at the other end of the equal (
) sign. We would like to replace this potential redunancy with a simple token instead, as this would also allow us to avoid ambiguity or figure out how to work such syntax into the "declarator" grammar construction (which is not impossible, just less palatable). Therefore, we do not pursue this option for the syntax of this feature. The other problem with this is that no function definition/declaration works in this manner, and such types are usually "adjusted" to being function pointers anyways.
4.6. The literal word _Alias
is the safe choice. Originally, we used the word
as there was very little option to create a keyword that more appropriately mirrors "
but for functions".
is already a prominent identifier in C codebases, and reusing
is not a very good idea for something that does not declare a type.
C++ took the keyword
, and so far it seems to have made most C and C++ developers stay away from the keyword altogether. Nevertheless, the wording uses a stand-in
. The suggestions we have for the token are as follows, based on not being findable in publicly available codebase sets (either on isocpp.org's code search of package manager code for Linux Distributions, GitHub’s dataset for code, and similar sources):
-
(made safer by C++ using it as a keyword) (however, C++ uses this as a type definition and does NOT include this for the ability to reference an existing function)using -
(long, pretty good name)using_alternate -
(long, pretty good name)alternate_alias -
, with a_Alias
header and a< stdalias . h >
in it (trying to avoid the underscore-capital keywords since some folk do not appreciate it)#define alias _Alias -
, since C++ has different semantics to this feature for the plain wordusing_ref
(which acts like a typedef)using -
(long enough it conflicts with no developers, recommended by Godbolt himself)sameysameynamename
Various names were also thought of and unfortunately discarded because they exist as macro names and identifier names in publicly available code today:
-
using_name -
/decl_alias declalias -
/alias_decl aliasdecl -
/alias_def aliasdef -
/name_decl namedecl -
/name_def namedef -
/name_alias namealias
While we use the word
right now as a stand-in for some of the prose above and below, we would appreciate feedback on what name to pick. The specification working has
to signify that we have not settled on a specific token (or token sequence) for the name at the moment. This does not preclude a more thorough look at a
-alike syntax. However, we do note that we do not employ declarator syntax and so a
-style syntax is not necessarily the best idea.
We would also like to note that C++ had a proposal to make the
syntax work for function declarations and also allow them to be renamed, just like this proposal[p0945]. However, the paper was dropped and never picked up and/or pursued in any other form again. It could be seen as taking design space or forcing a design upon C++ if we attempted to take the syntax
, and therefore while possible we would strongly advise against such a direction.
4.7. Variables
The original revisions of this paper did not include variables with it, since it was very specifically scoped to solve the function and ABI problems around functions. But, both the assembly labels,
redefinitions, and
syntaxes all support using this to rename variables. Therefore, alias declarations
There has been expressed want for transparent aliases for non-functions. That is:
int f ( int value ) { return value + value ; } int main () { int x = 1 ; _Alias y = x ; return & y == & x ? f ( y ) : 0 ; // returns f(x) == 2 }
We think this would be a useful general purpose compile-time renaming mechanism. Current implementations for both assembly labels and attributes allow this, and therefore we have added this functionality to the paper. Furthermore, it can allow for specific constructs constructs to be renamed and their old names to be marked with attributes. For example, occasionally APIs need to be reorganized for clarity purposes, but without fully breaking old code:
#include <stdio.h>const char * v1_name = "very.cool.name!" ; [[ deprecatad ( "this name will be removed in future versions, " "please upgrade appropriately" )]] _Alias v0_name = v1_name ; int main () { printf ( "v1: %s \n " , v1_name ); printf ( "v0: %s \n " , v0_name ); // diagnostic encouraged }
This can help migrate users to newer APIs that may change names or evolve over time, or offer better features/additional control parameters.
5. Implementation Experience
An implementation of Transparent Aliases is available at this Clang fork. It provides the feature and, when built and installed, can be used to run this suite of tests. A writeup of the effects of that implementation can be found in this blog post.
The test suite sets up a supposed ABI on both Linux, Mac, and Windows by creating a DLL with exported functions. Without redefining the functions, the tests show that taking an old Shared Object or a Windows DLL, upgrading it and doing an in-place replacement of that file with a newly built object that contains new definitions, still lets old applications work while new applications return the correct and proper functionality from the updated code.
The test suite also outputs a version number while doing a comparison test. A negative, custom-defined
number - which uses bits in all available registers on 2s complement 32-bit and 64-bit systems - is properly returned from a function
that computes its absolute value. That value is further compared equal to the size of a defined
, which resolves to
in the old application (
) and
in the new application (
):
#include <my_libc/maxabs.h>#include <stdio.h>int main () { intmax_t abi_is_hard = - ( intmax_t ) sizeof ( intmax_t ); intmax_t but_not_that_hard = maxabs ( abi_is_hard ); printf ( "%d \n " , my_libc_magic_number ()); return (( int )( but_not_that_hard ) == sizeof ( intmax_t )) ? 0 : 1 ; }
The program only returns
(and passes the test suite) if the returned value is properly negated and compares equal to the original size. The test output on all 3 of Linux (Ubuntu and Debian), Mac (Mojave), and Windows (Version 10 and 11) is as follows:
[ proc] Executing command: ctest -j10 -C Debug -T test --output-on-failure[ ctest] Cannot find file: transparent-aliases/.cmake/build/DartConfiguration.tcl[ ctest] Site:[ ctest] Build name:( empty) [ ctest] Cannot find file: transparent-aliases/.cmake/build/DartConfiguration.tcl[ ctest] Test project transparent-aliases/.cmake/build[ ctest] Start6 : discard_warning.compile.clean[ ctest] Start10 : app_old.lib_new-copy[ ctest] Start8 : app_old.lib_old[ ctest] Start9 : app_new.lib_new[ ctest] Start3 : example3[ ctest] Start5 : discard_warning[ ctest] Start4 : example4[ ctest] Start2 : example2[ ctest] Start1 : example1[ ctest] 1 /11 Test#6: discard_warning.compile.clean .... Passed 0.20 sec [ ctest] Start7 : discard_warning.compile[ ctest] 2 /11 Test#5: discard_warning .................. Passed 0.12 sec [ ctest] 3 /11 Test#10: app_old.lib_new-copy ............. Passed 0.20 sec [ ctest] Start11 : app_old.lib_new[ ctest] 4 /11 Test#4: example4 ......................... Passed 0.12 sec [ ctest] 5 /11 Test#2: example2 ......................... Passed 0.09 sec [ ctest] 6 /11 Test#1: example1 ......................... Passed 0.06 sec [ ctest] 7 /11 Test#8: app_old.lib_old .................. Passed 0.22 sec [ ctest] 8 /11 Test#9: app_new.lib_new .................. Passed 0.19 sec [ ctest] 9 /11 Test#3: example3 ......................... Passed 0.16 sec [ ctest] 10 /11 Test#11: app_old.lib_new .................. Passed 0.03 sec [ ctest] 11 /11 Test#7: discard_warning.compile .......... Passed 5.56 sec [ ctest] [ ctest] 100 % tests passed,0 tests failed out of11 [ ctest] [ ctest] Total Test time( real) = 5 .79 sec[ ctest] CTest finished withreturn code0
We specifically make sure to turn off typical automatic RPATH handling on *Nix machines, and make sure they load the DLL present in
(so that simply copying over the application-local DLL ensures it is loading the DLL associated with the test.
This serves as concrete evidence that existing implementations can use the feature to produce upgradable libraries while keeping old symbols entirely intact. Users using the test compiler could reproduce these results successfully.
5.1. What About Mangled Identifiers?
musl-libc, glibc, Darwin, and an excessively high amount of other standard library definitions make liberal use of both
and
/
labels to provide names in their binaries that do not represent typical mangling on existing systems. Below are snippets of such libraries and some declarations / code that they use to achieve this.
5.1.1. Darwin / MacOS 15.0
Apple’s MacOS 15.0 "Sequoia" currently deploys and has been deploying these fixes for a long time now. Using a macro named
, which translates to
the tokens put into that macro being stringified and concatenated with an extra underscore VIA the adjacent string literal
, several variables and functions in their base runtime have a mangling different from what would be expected of a typical loading of e.g. the
variable of the
function. A reproduction of that code can be found in § 8.2 MacOS 15.0 Sequoia _time.h Code.
6. Future Directions
6.1. Weak Declarations vs. Strong declarations
There were further suggestions to have the ability to make "
" aliases: that is, the ability to be able to define a compile-time alias, but be able to redeclare over it and completely erase the old declaration with an entirely incompatible/new declaration, at compile-time, with a new (and potentially incompatible) declaration. This is actually implemented in the current implementation, and it works as expected, but it is not provided in this paper.
However, weak versus strong is still a separate concern. It is part of a conceptually separate feature for linkers.
7. Wording
The following wording is against the latest draft of the C standard.
7.1. Modify "§6.2.1 Scopes of identifiers", paragraph 1
An identifier can denote:
a standard attribute, an attribute prefix, or an attribute name;
an object;
a function;
a tag or a member of a structure, union, or enumeration;
a typedef name;
- a transparent alias name;
a label name;
a macro name;
or a macro parameter.
7.2. Modify "§6.2.1 Scopes of identifiers", paragraph 4
Change every instance of "
declarator or type specifier
" to be "
declarator, transparent alias, or type specifier
".
7.3. Modify "§6.2.1 Scopes of identifiers", paragraph 7
Structure, union, and enumeration tags have scope that begins just after the appearance of the tag in a type specifier that declares the tag. Each enumeration constant has scope that begins just after the appearance of its defining enumerator in an enumerator list. An ordinary identifier that has an underspecified definition has scope that starts when the definition is completed; if the same ordinary identifier declares another entity with a scope that encloses the current block, that declaration is hidden as soon as the inner declarator is completed.19) A transparent alias name has a scope that begins at the end of its definition. Any other identifier has scope that begins just after the completion of its declarator.
7.4. Modify "§6.2.2 Linkages of identifiers", paragraph 6
The following identifiers have no linkage: an identifier declared to be anything other than an object or a function; a transparent alias; an identifier declared to be a function parameter; a block scope identifier for an object declared without the storage-class specifier extern.
7.5. Add transparent aliases to "§6.2.3 Name spaces of identifiers", paragraph 1, last bullet
…
- all other identifiers, called ordinary identifiers (declared in ordinary declarators , transparent aliases , or as enumeration constants).
7.6. Add a new keyword to "§6.4.1 Keywords", Syntax, paragraph 1
keyword: one of
…
- ALIAS-TOKEN
7.7. Modify §6.5.2 Primary expressions, Semantics, paragraph 2
An identifier is a primary expression, provided it has been declared as designating an object (in which case it is an lvalue)
or, a function (in which case it is a function designator) , or transparent alias (in which case it is either an lvalue or a function designator) .
7.8. Modify "§6.7 Declarations" as follows...
7.8.1. §6.7.1 General, Syntax, paragraph 1, with a new "declaration" production
declaration:
declaration-specifiers init-declarator-listopt ;
attribute-specifier-sequence declaration-specifiers init-declarator-list ;
static_assert-declaration
attribute-declaration
- transparent-alias-declaration
7.8.2. §6.7.1 General, Constraints, paragraphs 2 and 4
A declaration other than a static_assert, transparent alias, or attribute declaration shall declare at least…
If an identifier has no linkage, there shall be no more than one declaration of the identifier (in a declarator or type specifier) with the same scope and in the same name space, except that:
— a typedef name may be redefined to denote the same type as it currently does, provided that type is not a variably modified type;
- — a transparent alias name may be redefined as specified in 6.7.✨; and
— numeration constants and tags can be redeclared as specified in 6.7.3.3 and 6.7.3.4, respectively.
7.8.3. §6.7.1 General, Semantics, paragraph 7
A declaration specifies the interpretation and properties of a set of identifiers. A definition of an identifier is a declaration for that identifier that:
— for an object, causes storage to be reserved for that object;
— for a function, includes the function body;123)
— for an enumeration constant, is the (only) declaration of the identifier;
— for a typedef name, is the first (or only) declaration of the identifier
.; or- — for a transparent alias name, is the first (or only) declaration of the identifier.
7.9. Modify "§6.9 External definitions" paragraphs 3 and 5
There shall be no more than one external definition for each identifier declared with internal linkage in a translation unit. Moreover, if an identifier declared with internal linkage is used directly or indirectly (e.g., through a transparent alias) in an expression there shall be exactly one external definition for the identifier in the translation unit, ……
An external definition is an external declaration that is also a definition of a function (other than an inline definition) or an object. If an identifier declared with external linkage is used directly or indirectly (e.g., through a transparent alias) in an expression (other than as part of the operand of a typeof operator whose result is not a variably modified type, part of the controlling expression of a generic selection, part of the expression in a generic association that is not the result expression of its generic selection…
7.10. Add a new sub-clause "§6.7.✨ Transparent alias"
6.7.✨Transparent aliasesSyntaxtransparent-alias-declaration:
attribute-specifier-sequenceopt ALIAS-TOKEN identifier = identifier ;
DescriptionLet the identifier on the left hand side be the transparent alias name and the identifier on the right hand side be the transparent alias target.
ConstraintsA transparent alias target shall be an identifier which is an object, a function, or a transparent alias.
A transparent alias being redeclared shall refer to the same object or function as in its initial declarationFN0✨) and the type of that identifier shall be compatible with all visible declarations of the declared transparent alias.
FN0✨) If the transparent alias target points to another transparent alias, then the alias target is first resolved. The resolution occurs recursively until a function or object is the alias target. Resolution of a transparent alias target happens before the synonym is declared or redeclared, meaning a transparent alias name may refer to itself when it is being redeclared, but not when it is first declared. This resolution is not recomputed every time the transparent aliases is declared.SemanticsA transparent alias refers to an existing entity named by the transparent alias target. A transparent alias does not produce a new declaration; it is only a synonym for the transparent alias target specified. If the transparent alias target is another transparent alias, it is translated, recursively, until a non-transparent alias target is determined. A transparent alias target that is another transparent alias may be capable of referring to an entity that is no longer visible in the current scope.A transparent alias may be used in any context where the target may be used. A transparent alias has the same type as the transparent alias target it refers to when declared.The optional attribute specifier sequence before the ALIAS-TOKEN appertains to the transparent alias name.
EXAMPLE 1 The following program contains no constraint violations nor any failed assertions:
#include <assert.h>void do_work ( void ); void take_nap ( void ); ALIAS - TOKEN work_alias = do_work ; ALIAS - TOKEN nap_alias = take_nap ; ALIAS - TOKEN alias_of_work_alias = work_alias ; ALIAS - TOKEN alias_of_nap_alias = nap_alias ; int main () { assert ( & do_work == & work_alias ); assert ( & do_work == & alias_of_work_alias ); assert ( & work_alias == & alias_of_work_alias ); assert ( & take_nap == & nap_alias ); assert ( & take_nap == & alias_of_nap_alias ); assert ( & nap_alias == & alias_of_nap_alias ); assert ( & take_nap != & work_alias ); assert ( & do_work != & alias_of_nap_alias ); ALIAS - TOKEN local_work_alias = alias_of_work_alias ; assert ( & local_work_alias == & alias_of_work_alias ); do_work (); work_alias (); // calls do_work alias_of_work_alias (); // calls do_work local_work_alias (); // calls do_work take_nap (); nap_alias (); // calls take_nap alias_of_nap_alias (); // calls take_nap return 0 ; } EXAMPLE 2 Valid redeclarations:
int zzz ( int requested_sleep_time ); ALIAS - TOKEN sleep_alias = zzz ; ALIAS - TOKEN sleep_alias = sleep_alias ; ALIAS - TOKEN sleep_alias_alias = zzz ; ALIAS - TOKEN sleep_alias = sleep_alias_alias ; void func ( void ); int main () { // Inner scope: no constraint violation _Alias func = zzz ; } EXAMPLE 3 Invalid redeclarations:
int zzz ( int requested_sleep_time ); int truncated_zzz ( int requested_sleep_time ); ALIAS - TOKEN sleep_alias = sleep_alias ; // constraint violation: sleep_alias does // not exist until the // semicolon is reached ALIAS - TOKEN zzz = truncated_zzz ; // constraint violation: cannot hide // existing declaration ALIAS - TOKEN truncated_zzz = truncated_zzz ; // constraint violation: cannot change // function declaration // to transparent alias ALIAS - TOKEN valid_sleep_alias = zzz ; double valid_sleep_alias ( double requested_sleep_time ); // constraint violation: // redeclaring a // transparent alias with // non-compatible type EXAMPLE 4 An alias can be redeclared as either an alias or a function declaration so long as it is compatible:
double purr ( void ) { return 1.0 ; } ALIAS - TOKEN meow = purr ; double meow ( void ); // constraint violation: not a transparent alias ALIAS - TOKEN meow = purr ; // compatible redeclaration int main () { double x = meow (); // calls purr return ( int )( v ); // returns 1 } EXAMPLE 5 Compatible and completed types versus aliases:
void otter ( int ( * )[]); ALIAS - TOKEN water_noodle = otter ; void otter ( int ( * )[ 2 ]); static_assert ( _Generic ( typeof ( water_noodle ), void ( int ( * )[]) : 1 , default : 0 )); EXAMPLE 6 Shadowing and compatible types through aliases:
void cookie ( int ( * )[ 2 ]); int main () { ALIAS - TOKEN biscuit = cookie ; { int cookie ; // Shadow outer declaration. { // biscuit refers to declaration it was created with static_assert ( _Generic ( typeof ( biscuit ), void ( int ( * )[ 2 ]) : 1 , default : 0 )); } } return 0 ; } EXAMPLE 7 Composite and compatible types through aliases:
void otter ( int ( * )[], int ( * )[ 2 ]); int main () { ALIAS - TOKEN water_sausage = otter ; { void otter ( int ( * )[ 2 ], int ( * )[]); static_assert ( _Generic ( typeof ( water_sausage ), void ( int ( * )[], int ( * )[ 2 ]) : 1 , default : 0 )); } } EXAMPLE 8 Equivalence of object and address through an alias:
int f ( int value ) { return value + value ; } int main () { const int x = 1 ; ALIAS - TOKEN y = x ; // equivalent to: // return 2; return & y == & x ? f ( y ) : 0 ; } EXAMPLE 9 Aliasing object declarations and adding attributes to aliases:
#include <stdio.h>#define V0_DEPRECATED 1 const char * v1_name = "treehouse.systems!" ; #if defined(V0_DEPRECATED) && (V0_DEPRECATED != 0) [[ deprecatad ( "v0_name will be removed in version 2, please upgrade" )]] #endif ALIAS - TOKEN v0_name = v1_name ; int main () { printf ( "v1: %s \n " , v1_name ); printf ( "v0: %s \n " , v0_name ); // diagnostic encouraged } Recommended Practice
Implementations and programs may use aliases as a way to produce stability for translation units which rely on specific function declaration and definitions being present while aliasing a common declaration name for a more suitable interface. It may be particularly helpful for function declarations which use type definitions (6.7.8) in return and parameter types. Programs may update and upgrade alias definitions alongside type definitions to preserve entities and symbols in a given program while letting newer programs take advantage of upgraded functionality.EXAMPLE 10 Versioning of a function call
while keeping old externally-defined function definitions available within the program. The below snippets use some exposition-only implementation-provided signed integer types
imaxabs , and
__int128_t within a
__int256_t file:
imaxabs . h extern __int128 __imaxabs_128ish ( __int128 value ); extern __int256 __imaxabs_256ish ( __int256 value ); #if VER1 typedef __int128 intmax_t ; ALIAS - TOKEN imaxabs = __imaxabs_128ish ; #elif VER2 typedef __int256_t intmax_t ; ALIAS - TOKEN imaxabs = __imaxabs_256ish ; #else typedef long long intmax_t ; extern long long imaxabs ( long long value ); #endif and, with a
file:
imaxabs . c // <imaxabs.h> NOT included, intentionally extern __int128 __imaxabs_128ish ( __int128 value ) { if ( value < 0 ) return - value ; return value ; } extern __int256 __imaxabs_256ish ( __int256 value ) { if ( value < 0 ) return - value ; return value ; } // legacy external definition extern long long imaxabs ( long long value ) { if ( value < 0 ) return - value ; return value ; }
8. Appendix
A collection of code snippets, old information, and notes for the rest of the paper.
8.1. FreeBSD cdefs . h
with __sym_compat
macro
FreeBSD uses a [sequence of macros to provide various levels of compatibility, all using various strengths and flavors of
labels](https://github.com/freebsd/freebsd-src/blob/main/sys/sys/cdefs.h#L369-L399):
// … #define __strong_reference(sym,aliassym) \ extern __typeof (sym) aliassym __attribute__ ((__alias__ (#sym))) #ifdef __STDC__ #define __weak_reference(sym,alias) \ __asm__(".weak " #alias); \ __asm__(".equ " #alias ", " #sym) #define __warn_references(sym,msg) \ __asm__(".section .gnu.warning." #sym); \ __asm__(".asciz \"" msg "\""); \ __asm__(".previous") #ifdef __CC_SUPPORTS_SYMVER #define __sym_compat(sym,impl,verid) \ __asm__(".symver " #impl ", " #sym "@" #verid) #define __sym_default(sym,impl,verid) \ __asm__(".symver " #impl ", " #sym "@@@" #verid) #endif #else #define __weak_reference(sym,alias) \ __asm__(".weak alias"); \ __asm__(".equ alias, sym") #define __warn_references(sym,msg) \ __asm__(".section .gnu.warning.sym"); \ __asm__(".asciz \"msg\""); \ __asm__(".previous") #ifdef __CC_SUPPORTS_SYMVER #define __sym_compat(sym,impl,verid) \ __asm__(".symver impl, sym@verid") #define __sym_default(impl,sym,verid) \ __asm__(".symver impl, sym@@@verid") #endif #endif /* __STDC__ */ // …
This gets used effectively everywhere in FreeBSD for compatibility purposes, such as in libgen.h.
8.2. MacOS 15.0 Sequoia _time . h
Code
This is from the MacOS 15.0 Sequoia release, and comes from its
internal header. The specific macro that shows a redirect is the
macro, which is a simple
label.
/* * Copyright (c) 2023 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * Copyright (c) 1989, 1993 * The Regents of the University of California. All rights reserved. * (c) UNIX System Laboratories, Inc. * All or some portions of this file are derived from material licensed * to the University of California by American Telephone and Telegraph * Co. or Unix System Laboratories, Inc. and are reproduced herein with * the permission of UNIX System Laboratories, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)time.h 8.3 (Berkeley) 1/21/94 */ #ifndef _TIME_H_ #define _TIME_H_ /* ... SOME CODE OMITTED ... */ extern int getdate_err ; #if __DARWIN_UNIX03 extern long timezone __DARWIN_ALIAS ( timezone ); #endif /* __DARWIN_UNIX03 */ extern int daylight ; __BEGIN_DECLS char * asctime ( const struct tm * ); clock_t clock ( void ) __DARWIN_ALIAS ( clock ); char * ctime ( const time_t * ); double difftime ( time_t , time_t ); struct tm * getdate ( const char * ); struct tm * gmtime ( const time_t * ); struct tm * localtime ( const time_t * ); time_t mktime ( struct tm * ) __DARWIN_ALIAS ( mktime ); size_t strftime ( char * __restrict , size_t , const char * __restrict , const struct tm * __restrict ) __DARWIN_ALIAS ( strftime ); char * strptime ( const char * __restrict , const char * __restrict , struct tm * __restrict ) __DARWIN_ALIAS ( strptime ); time_t time ( time_t * ); /* ... SOME CODE OMITTED ... */
8.3. musl-libc Aliasing/Renaming Features
musl-libc redirects function names frequently to prevent ABI collisions and compatibility failures. From [musl-libc’s
header](https://github.com/bminor/musl/blob/047a16398b29d2702a41a0d6d15370d54b9d723c/include/features.h#L38):
// … #define __REDIR(x,y) __typeof__(x) x __asm__(#y) // …
8.3.1. musl-libc "redirect
" Functionality
The redirect macro shown previously has over 50 uses in the musl-libc library, primarily to change the name of functions that contain the time type definition. An [example of this is in
](https://github.com/bminor/musl/blob/047a16398b29d2702a41a0d6d15370d54b9d723c/include/sys/time.h#L59-L71):
// … #if _REDIR_TIME64 __REDIR ( gettimeofday , __gettimeofday_time64 ); __REDIR ( getitimer , __getitimer_time64 ); __REDIR ( setitimer , __setitimer_time64 ); __REDIR ( utimes , __utimes_time64 ); #if defined(_GNU_SOURCE) || defined(_BSD_SOURCE) __REDIR ( futimes , __futimes_time64 ); __REDIR ( futimesat , __futimesat_time64 ); __REDIR ( lutimes , __lutimes_time64 ); __REDIR ( settimeofday , __settimeofday_time64 ); __REDIR ( adjtime , __adjtime64 ); #endif #endif // …
8.3.2. musl-libc 32-bit time compatibility compatibility shim
From [musl-libc’s
header](https://github.com/bminor/musl/blob/047a16398b29d2702a41a0d6d15370d54b9d723c/compat/time32/time32.h#L1-L91):
#ifndef TIME32_H #define TIME32_H #include <sys/types.h>typedef long time32_t ; struct timeval32 { long tv_sec ; long tv_usec ; }; struct itimerval32 { struct timeval32 it_interval ; struct timeval32 it_value ; }; struct timespec32 { long tv_sec ; long tv_nsec ; }; struct itimerspec32 { struct timespec32 it_interval ; struct timespec32 it_value ; }; int __adjtime32 () __asm__ ( "adjtime" ); int __adjtimex_time32 () __asm__ ( "adjtimex" ); int __aio_suspend_time32 () __asm__ ( "aio_suspend" ); int __clock_adjtime32 () __asm__ ( "clock_adjtime" ); int __clock_getres_time32 () __asm__ ( "clock_getres" ); int __clock_gettime32 () __asm__ ( "clock_gettime" ); int __clock_nanosleep_time32 () __asm__ ( "clock_nanosleep" ); int __clock_settime32 () __asm__ ( "clock_settime" ); int __cnd_timedwait_time32 () __asm__ ( "cnd_timedwait" ); char * __ctime32 () __asm__ ( "ctime" ); char * __ctime32_r () __asm__ ( "ctime_r" ); double __difftime32 () __asm__ ( "difftime" ); int __fstat_time32 () __asm__ ( "fstat" ); int __fstatat_time32 () __asm__ ( "fstatat" ); int __ftime32 () __asm__ ( "ftime" ); int __futimens_time32 () __asm__ ( "futimens" ); int __futimes_time32 () __asm__ ( "futimes" ); int __futimesat_time32 () __asm__ ( "futimesat" ); int __getitimer_time32 () __asm__ ( "getitimer" ); int __getrusage_time32 () __asm__ ( "getrusage" ); int __gettimeofday_time32 () __asm__ ( "gettimeofday" ); struct tm * __gmtime32 () __asm__ ( "gmtime" ); struct tm * __gmtime32_r () __asm__ ( "gmtime_r" ); struct tm * __localtime32 () __asm__ ( "localtime" ); struct tm * __localtime32_r () __asm__ ( "localtime_r" ); int __lstat_time32 () __asm__ ( "lstat" ); int __lutimes_time32 () __asm__ ( "lutimes" ); time32_t __mktime32 () __asm__ ( "mktime" ); ssize_t __mq_timedreceive_time32 () __asm__ ( "mq_timedreceive" ); int __mq_timedsend_time32 () __asm__ ( "mq_timedsend" ); int __mtx_timedlock_time32 () __asm__ ( "mtx_timedlock" ); int __nanosleep_time32 () __asm__ ( "nanosleep" ); int __ppoll_time32 () __asm__ ( "ppoll" ); int __pselect_time32 () __asm__ ( "pselect" ); int __pthread_cond_timedwait_time32 () __asm__ ( "pthread_cond_timedwait" ); int __pthread_mutex_timedlock_time32 () __asm__ ( "pthread_mutex_timedlock" ); int __pthread_rwlock_timedrdlock_time32 () __asm__ ( "pthread_rwlock_timedrdlock" ); int __pthread_rwlock_timedwrlock_time32 () __asm__ ( "pthread_rwlock_timedwrlock" ); int __pthread_timedjoin_np_time32 () __asm__ ( "pthread_timedjoin_np" ); int __recvmmsg_time32 () __asm__ ( "recvmmsg" ); int __sched_rr_get_interval_time32 () __asm__ ( "sched_rr_get_interval" ); int __select_time32 () __asm__ ( "select" ); int __sem_timedwait_time32 () __asm__ ( "sem_timedwait" ); int __semtimedop_time32 () __asm__ ( "semtimedop" ); int __setitimer_time32 () __asm__ ( "setitimer" ); int __settimeofday_time32 () __asm__ ( "settimeofday" ); int __sigtimedwait_time32 () __asm__ ( "sigtimedwait" ); int __stat_time32 () __asm__ ( "stat" ); int __stime32 () __asm__ ( "stime" ); int __thrd_sleep_time32 () __asm__ ( "thrd_sleep" ); time32_t __time32 () __asm__ ( "time" ); time32_t __time32gm () __asm__ ( "timegm" ); int __timer_gettime32 () __asm__ ( "timer_gettime" ); int __timer_settime32 () __asm__ ( "timer_settime" ); int __timerfd_gettime32 () __asm__ ( "timerfd_gettime" ); int __timerfd_settime32 () __asm__ ( "timerfd_settime" ); int __timespec_get_time32 () __asm__ ( "timespec_get" ); int __utime_time32 () __asm__ ( "utime" ); int __utimensat_time32 () __asm__ ( "utimensat" ); int __utimes_time32 () __asm__ ( "utimes" ); pid_t __wait3_time32 () __asm__ ( "wait3" ); pid_t __wait4_time32 () __asm__ ( "wait4" ); #endif