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.