1. Revision History
1.1. Revision 0 - May 6th, 2025
-
Initial release. ✨
2. Introduction & Motivation
The majority of the motivation is the same as it was 30 years ago, 20 years, and 10 years ago. In [n2230], Martin Sebor captured the core of the motivation:
... A void pointer is not convertible to a function pointer, and conversely, function pointers aren’t convertible to void. It is conceivable that an implementation may (and some, in fact, do) define function pointers to have a different size or representation than void, and such conversions could result in the corruption of the opriginal value.
However, C does not specify an analogous generic function pointer. Unlike object types, a pointer to a function of one type can be converted to a pointer to a function of any other type and back with no change. This is a useful guarantee that makes function pointers safely interchangeable (except to call a function, of course). At the same time, the absence of a generic function pointer analogous to void* has led to a proliferation of workarounds in applications, libraries, and implementations alike. ...
Popular distributions, operating systems, and platforms such as POSIX, Windows/NT, and many other platforms require that a data pointer (
) is the same size as any function pointer (
). But, for many other platforms, function pointers are not the same size as data pointers altogether, either because there are multiple data segments and the data pointers are larger or -- less common but still present -- because function pointers are larger than data pointers. Rather than try to force
to change to patch this hole (which would be a very fundamental ABI break on many platforms), this proposal focuses instead on following in [n2230]'s footsteps and proposing a (core language) type to satisfy this need properly.
3. Design
The goal of this paper is to provide a single type,
, from the core language. It’s a core language type that is incomplete by itself, much like
. Additionally, a pointer to
has the requirement that it is large enough to hold any function pointer type, and is implicitly convertible from and to any such pointer type, much like its
counterpart. It is modeled directly after
and modifies the same places in the wording.
3.1. Is It Callable?
It is NOT callable.
KnR functions had a serious flaw where despite being usable as "omni" function pointer types (ignoring the issue of return values and potential mismatches there): KnR functions had a different ABI than
functions and were not required to have the same ABI as other function types. For example, a function
was not required to have the exact same calling convention, register allocation, and other important binary details as a KnR function
. Similarly, a function
or
was not required to have the same ABI details as either
or
, even under the circumstances that they were all called like so:
int a = 0 ; double b = 1 ; func ( a , b ); func2 ( a , b ); func3 ( a , b ); func4 ( a , b );
Storing
in a KnR function pointer
and then calling it as such was not required to work in any version of C (it’s explicitly undefined behavior in every version of C, see "6.3.3.3 Pointers" in the latest working draft and similar paragraphs in C23, C17, and further back). It just so happens that it did happen to work (though technically can and could fail in invisible-to-the-Abstract-Machine ways) for the vast majority of existing C code, due to how most ABIs tend to be designed. (There were ABI issues between
and
, in particular when people would define
as a KnR function on some platforms while competing with
and similar definitions on various extern-and-exported-interface libraries.)
3.2. Library Modifications
The library needs further modification to allow for passing this new pointer type in, since
by itself is specified to handle just
. Since function pointer types and
may not be the same size, a new entry is required to not taint the ABI of these functions, particularly of
and
.
There were a bunch of potential candidates but a lot of them had problems both because of existing uses in implementations or down-stream, or they were novel new modifiers. For example,
is already being used by CHERI, while
is already taken by typical POSIX shell-based
for quotable strings. To further disadvantage
, it -- despite being a logical new letter next to
-- is taken by BSD and still has some existing uses, even though the type it’s used for (
) has long since been aliased to a "proper" type by now. Trying to sneak in
is awkward thanks to implementation already moving in on
, especially if printing address spaces as part of pointers because a thing where length modifiers and precision get used to represent it.
seems nice for the mnemonic "instruction pointer", but
is already a normal conversion specifier and
is novel in this space as a modifier and a not a standalone conversion specifier.
Thusly, we settled for
in this proposal. It can be read mnemonically as "jump pointer", which is reminiscent of assembly
and for assembly junkies gives the feeling of both
and
.
is also, already, a length modifier and has no known uses in the wild in combination with
. It is the best we can do given the Wild Wild West of specifiers and modifiers in the C-and-derivative ecosystem, at this point.
3.3. ... ptr
In the Name or a Pointer to Incomplete Type?
The goal of this design was to feel as close to
as possible, since that is what this type represents. This means that we wanted to have the
present to have it look and feel just like a regular pointer type under regular pointer rules, rather than having it be part of the name. Since this is a core language type and a not a library definition, we preferred to define it closer to
than anything else. It also helps in putting this type into the specification, as all we have to do is mirror exactly the specification style and places that "A pointer to
" and "
" exist in the current standard. This eases integration and is -- after first explanation -- immediately understandable to those who have been using
as the omni-object pointer.
3.4. Giving void *
Additional Powers
Furthermore, as this is defined in the core language, we can also provide a core language predefined macro for whether or not
->
and vice-versa is an allowable, legal translation. This also allows us to put wording in the core language that blesses such an (implicit) conversion, given that it’s highly prevalent on a wide variety of architectures. It is often seen as a "gotcha" that this conversion doesn’t work, and users have been asking for this change for a very long time. We obviously do not want to make things worse for embedded platforms or make them non-conforming; they are an important bedrock of C.
Implementations which define the conditionally-supported environment macro
enables the
🔄
🔄
chain of changes. This would simply not be defined for implementations that could not handle it (e.g. 16-bit Texas Instrument DSPs or 8-bit multi-address-space chips that have 21 bit function pointers but 16 bit memory addresses). Most modern implementations -- Windows/NT since forever and POSIX since the 90s with XSI extensions (and just simply required by POSIX 2018) -- would have this defined, and users could
inside of an
check if their code happens to rely on this property.
3.5. Naming
The name chosen here is the one that had the no clashes in both its reserved form
and it’s header form
. The following other names were considered and searched for in public (and some private) code repositories for their prevalence as possible name candidates:
-
with_Func_ptr_t
(with nofunc_ptr_t
required for use)* -
with_Any_func_ptr_t
(with noany_func_ptr_t
required for use)* -
with_Fn_ptr_t
(with nofn_ptr_t
required for use)* -
with_Fn
orfn
(user addsstdc_fn
to make pointer)* -
with_Func
orfunc
(user addsstd_func_t
to make pointer)* -
with_Func_t
orfunc_t
(user addsstdc_func_t
to make pointer)*
Many of these had issues with existing standard library type names (e.g.
and
for C++ type names in templates and typedefs for standard or standard-adjacent libraries) or had common names that, even if hidden behind a header include, would result in undue naming burden on potential downstream users (
or
or
). Other permutations of these names, including ones with
/
or
and
, either clashed with existing code or became undesirably long.
4. Wording
The following wording is against the latest draft of the C standard.
4.1. Modify §6.2.5 "Types"
6.5.2Types...
...
The
type comprises the concept of a function; it is an incomplete object type that cannot be completed.
_Any_func ...
A pointer to
shall have the same representation and alignment requirements as a pointer to a character type.41) Similarly, pointers to qualified or unqualified versions of compatible types shall have the same representation and alignment requirements. A pointer to
void shall have an alignment and representation suitable for the storage of any function pointer type. All pointers to structure types shall have the same representation and alignment requirements as each other. All pointers to union types shall have the same representation and alignment requirements as each other. Pointers to other types may not have the same representation or alignment requirements.
_Any_func
4.2. Modify §6.3.3.3 "Pointers"
6.3.3.3Pointers......A pointer to
can be converted to or from a pointer to any function type. A pointer to any function type can be converted to a pointer to
_Any_func and back again; the result shall compare equal to the original pointer.
_Any_func If and only if
is defined and has a strictly positive value, a pointer to
__STDC_IMPLICIT_ANY_FUNC_PTR_TO_VOID_PTR__ can be converted to or from a pointer to
_Any_func . A pointer to
void can be converted to a pointer to
void and back again; the result shall compare equal to the original pointer.
_Any_func If and only if
is defined and has a strictly positive value, a pointer to
__STDC_IMPLICIT_ANY_FUNC_PTR_TO_VOID_PTR__ can be converted to or from a pointer to any function type. A pointer to any function type can be converted to a pointer to
void and back again; the result shall compare equal to the original pointer.
void
4.3. Modify §6.4.2 "Keywords" to add a new keyword
6.4.2KeywordsSynopsiskeyword: one of
... ... ... ...
while ... ... ... ...
_Any_func ... ... ... ...
_Atomic ... ... ... ... ...
4.4. Modify §6.5.16 "Conditional operator"
6.5.16Conditional operator...Constraints...One of the following shall hold: ...
...
; orone operand is a pointer to an object type and the other is a pointer to a qualified or unqualified version of
void .;- if and only if
is defined and has a strictly positive value, one operand is a pointer to a function type and the other is a pointer to a qualified or unqualified version of
__STDC_IMPLICIT_ANY_FUNC_PTR_TO_VOID_PTR__ ; or
void - if and only if
is defined and has a strictly positive value, one operand is a pointer to
__STDC_IMPLICIT_ANY_FUNC_PTR_TO_VOID_PTR__ and the other is a pointer to a qualified or unqualified version of
_Any_func ; or
void - one operand is a pointer to a function type and the other is a pointer to a qualified or unqualified version of
.
_Any_func ...If both the second and third operands are pointers, the result type is a pointer to a type qualified with all the type qualifiers of the types referenced by both operands; if one is a null pointer constant (other than a pointer) or has type
and the other is a pointer, the result type is the pointer type; if both the second and third operands have
nullptr_t type, the result also has that type. Furthermore, if both operands are pointers to compatible types or to differently qualified versions of compatible types, the result type is a pointer to an appropriately qualified version of the composite type; if one operand is either a null pointer constant or a pointer to qualified or unqualified
nullptr_t , the result has the type of the other operand; otherwise, one operand is a pointer to
_Any_func or a qualified version of
void , in which case the result type is a pointer to an appropriately qualified version of
void .
void
4.5. Modify §6.5.17.2 "Simple assignment"
6.5.17.2Simple assignmentConstraintsOne of the following shall hold: ...
...
the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) one operand is a pointer to an object type, and the other is a pointer to a qualified or unqualified version of
, and the type pointed to by the left operand has all the qualifiers of the type pointed to by the right operand;
void - if and only if
is defined and has a strictly positive value and the left operand is atomic, qualified, or unqualified pointer to
__STDC_IMPLICIT_ANY_FUNC_PTR_TO_VOID_PTR__ , and the other is a pointer to a qualified or unqualified version of
_Any_func , and the type pointed to by the left operand has all the qualifiers of the type pointed to by the right operand;
void - the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) one operand is a pointer to a function type, and the other is a pointer to a qualified or unqualified version of
, and the type pointed to by the left operand has all the qualifiers of the type pointed to by the right operand;
_Any_func - if and only if
is defined and has a strictly positive value, the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) one operand is a pointer to an function type, and the other is a pointer to a qualified or unqualified version of
__STDC_IMPLICIT_ANY_FUNC_PTR_TO_VOID_PTR__ , and the type pointed to by the left operand has all the qualifiers of the type pointed to by the right operand;
void ...
4.6. Modify §6.10.10.3 "Environment macros"
6.10.10.3Environment macrosThe following macro names are conditionally defined by the implementation:
... ...
__STDC_IMPLICIT_ANY_FUNC_PTR_TO_VOID_PTR__ A strictly positive integer constant expression if an implementation supports converting any function pointer type, or a pointer to , to
_Any_func implicitly and vice-versa (6.3.3.3, 6.5.17.2).
void * ... ...
4.7. Modify §7.24.6.2 "The fprintf
function"
7.24.6.2Thefunction
fprintf Synopsis#include <stdio.h>int fprintf ( FILE * restrict stream , const char * restrict format , ...); Description...
The length modifiers and their meanings are:
... ...
j Specifies that a following ,
b ,
B ,
d ,
i ,
o ,
u , or
x conversion specifier applies to an
X or
intmax_t argument;
uintmax_t orthat a followingconversion specifier applies to a pointer to an
n argument ; or, a following
intmax_t conversion specifier applies to an
p argument .
_Any_func * ... ... The conversion specifiers and their meanings are:
... ...
p The argument shall be a pointer to
void or,a pointer to a character type or a pointer to. The value of the pointer is converted to a sequence of printing characters, in an implementation-defined manner.
_Any_func ... ...
4.7.1. NOTE: IDENTICAL CHANGES TO fwprintf
!
4.7.2. NOTE: UPDATE ANNEX J UNDEFINED BEHAVIOR (Number 34)!
4.8. Modify §7.24.6.3 "The fscanf
function"
7.24.6.3Thefunction
fscanf Synopsis#include <stdio.h>int fscanf ( FILE * restrict stream , const char * restrict format , ...); Description...
The length modifiers and their meanings are:
... ...
j Specifies that a following ,
b ,
d ,
i ,
o ,
u ,
x , or
X conversion specifier applies to an argument with type pointer to
n or
intmax_t ; or, a following
uintmax_t conversion specifier applies to an argument with type pointer to pointer of
p .
_Any_func ... ... In the following, the type of the corresponding argument for a conversion specifier shall be a pointer to a type determined by the length modifiers, if any, or specified by the conversion specifier. The conversion specifiers and their meanings are:
... ...
p Matches an implementation-defined set of sequences, which should be the same as the set of sequences that may be produced by the or
% p conversion of the
% jp function. The corresponding argument shall be a pointer to a pointer of void ; or, with the
fprintf length modifier, a pointer to pointer of
j . The input item is converted to a pointer value in an implementation-defined manner. If the input item is a value converted earlier during the same program execution, the pointer that results shall compare equal to that value; otherwise the behavior of the
_Any_func or
% p conversion is undefined.
% jp ... ...