How to print pointer to function?

David Brown david@westcontrol.com
Wed Dec 18 10:05:00 GMT 2019


On 18/12/2019 03:39, Liu Hao wrote:
> 脭脷 2019/12/18 脡脧脦莽12:30, Vincent Lefevre 脨麓碌脌:
>> On 2019-12-17 16:27:28 +0100, Manfred wrote:
>>> On 12/17/2019 2:22 PM, Segher Boessenkool wrote:
>>>> Is there *any* portable way to print function pointers?  Other than
>>>> accessing it as bytes :-)
>>>
>>> n1570 section 6.3.2.3 p6 says "Any pointer type may be converted to an
>>> integer type. Except as previously specified, the result is
>>> implementation-defined. ..."
>>>
>>> It says implementation-defined, not undefined,
>>
> 
> There's an enormous number of implementation-defined things, which
> people kind of rely on to write 'portable code':

It's entirely possible to write portable code that relies on
implementation-dependent behaviour.  The code can work correctly on
different systems, but perhaps with different results.  Sometimes you
have to have conditional compilation or similar tricks to get things to
work as needed.  In practice, often you can write code that is portable
to any "reasonable" implementation, and accept that the code won't work
for other systems:

#include <limits.h>
#if -INT_MAX == INT_MIN
#error Only two's complement signed integers supported by this code
#endif

Checking for lack of padding bits can be done too, I believe.


> 
> 0. Conversion a value from `unsigned int` to `signed char` which doesn't
>    fit in it yields an implementation-defined result.
>    [C++14 now requires 2's complement, which is required by GCC.]

IIRC it is C++20 that limits the signed integer representation to two's
complement.  Prior to that, I think C++ is actually more flexible than C
in the standards - but I don't think there are any known C++
implementations that are /not/ two's complement.  (The few remaining
ones' complement and signed magnitude C compilers are dinosaurs.)

Note that it is entirely possible for an implementation to have two's
complement representation but /not/ use modulo to reduce a value to fit
into a smaller signed type.  In particular, a compiler could choose to
raise a signal and halt with an error message (I don't know if any of
gcc's sanitizers do that).  But AFAIUI with C++20, and C20, such an
option would be non-conforming.

It is quite possible to write code for such conversions that is
portable.  This will handle most cases:

#include <limits.h>

signed char conv_uint_to_schar(unsigned int x) {
    unsigned int y = x % (UCHAR_MAX + 1);
    if (y <= SCHAR_MAX) return y;
    int z = (int) y - (SCHAR_MAX - SCHAR_MIN + 1);
    return z;
}

I think it will work with ones' complement and signed magnitude, and
with two's complement implementations with different conversion
behaviour.  (It does not cover compilers where "char" and "int" are the
same size, which is actually more realistic than different signed
integer representations.)  And gcc complies this to the same single
"mov" instruction that it uses for an implicit conversion.

> 1. Shifting a negative integer to the right yields an impl-def result.
>    Shifting a negative integer to the left is undefined behavior.
>    [Ditto.]

Many coding standards ban bit-manipulation operations (shifts and
bitwise operators) on signed types.  Stick to unsigned types and the
code is much more portable - and usually makes more sense.

> 2. Calling `fflush()` on an input stream results in undefined behavior.
>    [The behavior is defined by POSIX 2008.]

Implementations can always define the behaviour for things that are
undefined in the standards.  This is not an example of
implementation-defined behaviour, but of behaviour defined in additional
standards.  That is common in C (indeed in all programming).  If your
code relies on POSIX features, you can make it portable across POSIX
systems - but it will not be portable to non-POSIX systems.

> 3. Comparing two pointers that do not point to elements or past-the-end
>    position of the same array, using one of <, >, <= or >= operators,
>    results in undefined behavior.

So don't do that.  (This does mean that you can't implement functions
like memmove efficiently in portable, standards-only C.)

There are processors with C compilers where there are different memory
spaces, and it really does not make sense to compare pointers to them.
gcc supports at least one such device (the AVR).  There are also
processors where memory pointers and comparison can be complicated, such
as segmented x86 memory models.

On most systems, you can convert the pointers to "uintptr_t" and compare
those.  This will work correctly, to the extent that the comparison
makes sense, on any system which implements uintptr_t.

>    [C++ says the result is unspecified.]

That can sometimes make more sense, but it still won't let you write
memmove.

> 
> So how can you write 'portable code' without regard to these facts?
> 

No problem - don't use any of these features.

Portability only makes sense for some kinds of code - and even then, it
usually means "portable across similar systems".  The smallest system I
have programmed using gcc had 2 KB of program memory space and no ram at
all.  What kind of code would need to be portable from such a device, to
PC's, supercomputers, and forty year old mainframes?

When you write some code, you first figure out what the code should do.
 Then you can think about what range of systems it makes sense to
support.  Portable coding is then usually fairly easy as long as the
target range is sensible.

> In addition, even if it could be 'portable' to have standard casts
> between `void*` and function pointers, the `%p` specifier of `printf()`
> is still impl-def. So how can you make such combination more 'portable'?
> 
> 

The details of pointers is /always/ going to be implementation specific,
since pointers are implemented in different ways in different systems.



More information about the Gcc-help mailing list