1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
|
===================
Type-checking casts
===================
The C++ language provides “new-style casts”, referring to the four
template-looking invocations ``static_cast<>``, ``const_cast<>``,
``reinterpret_cast<>`` and ``dynamic_cast<>``. No such blessing was given to
the C language, but still, even using macros that expand to the olde cast make
it much easier to find casts in source code and annotate why something was
casted, which is already an improvement. — Actually, it is possible to do a
some type checking, using some GCC extensions, which augments these macros from
their documentary nature to an actual safety measure.
``reinterpret_cast``
====================
``reinterpret_cast()`` maps directly to the old-style typecast,
``(type)(expr)``, and causes the bit pattern for the ``expr`` rvalue to be
“reinterpreted” as a new type. You will notice that “reinterpret” is the
longest of all the ``*_cast`` names, and can easily cause lines to grow beyond
80 columns (the good maximum in many style guides). As a side effect, it is a
good indicator that something potentially dangerous might be going on, for
example converting intergers from/to pointer.
.. code-block:: c
#include <libHX/defs.h>
int i;
/* Tree with numeric keys */
tree = HXhashmap_init(0);
for (i = 0; i < 6; ++i)
HXmap_add(tree, reinterpret_cast(void *,
static_cast(long, i)), my_data);
``signed_cast``
===============
This tag is for annotating that the cast was solely done to change the
signedness of pointers to char — and only those. No integers etc. The intention
is to facilitate working with libraries that use ``unsigned char *`` pointers,
such as libcrypto and libssl (from the OpenSSL project) or libxml2, for
example. See table [tab:defs-signed_cast] for the allowed conversions. C++ does
not actually have a ``signed_cast<>``, and one would have to use
``reinterpret_cast<>`` to do the conversion, because ``static_cast<>`` does not
allow conversion from ``const char *`` to ``const unsigned char *``, for
example. (libHX's ``static_cast()`` would also throw at least a compiler
warning about the different signedness.) This is where signed_cast comes in.
(libHX provides a ``signed_cast<>`` for C++ though.)
.. table :: Accepted conversions for ``signed_cast()``
+-----------------------+----+-----+-----+-----+------+------+
| From \ To | c* | sc* | uc* | Cc* | Csc* | Cuc* |
+=======================+====+=====+=====+=====+======+======+
| char * | ok | ok | ok | ok | ok | ok |
+-----------------------+----+-----+-----+-----+------+------+
| signed char * | ok | ok | ok | ok | ok | ok |
+-----------------------+----+-----+-----+-----+------+------+
| unsigned char * | ok | ok | ok | ok | ok | ok |
+-----------------------+----+-----+-----+-----+------+------+
| const char * | – | – | - | ok | ok | ok |
+-----------------------+----+-----+-----+-----+------+------+
| const signed char * | – | – | – | ok | ok | ok |
+-----------------------+----+-----+-----+-----+------+------+
| const unsigned char * | – | – | – | ok | ok | ok |
+-----------------------+----+-----+-----+-----+------+------+
``static_cast``
===============
Just like C++'s ``static_cast<>``, libHX's ``static_cast()`` verifies that
``expr`` can be implicitly converted to the new type (by a simple ``b = a``).
Such is mainly useful for forcing a specific type, as is needed in varargs
functions such as ``printf``, and where the conversion actually incurs other
side effects, such as truncation or promotion:
.. code-block:: c
/* Convert to a type printf knows about */
uint64_t x = something;
printf("%llu\n", static_cast(unsigned long long, x));
Because there is no format specifier for ``uint64_t`` for ``printf`` (well yes,
there is ``PRIu64``), a conversion to an accepted type is necessary to not
cause undefined behavior. Code that does, for example, ``printf("%u")`` on a
``long`` only happens to work on architectures where ``sizeof(unsigned int) ==
sizeof(unsigned long)``, such as i386. On x86_64, an ``unsigned long`` is
usually twice as big as an ``unsigned int``, so that 8 bytes are pushed onto
the stack, but printf only unshifts 4 bytes because the developer indicated
``%u``, leading to misreading the next variable on the stack.
.. code-block:: c
/* Force promotion */
double a_quarter = static_cast(double, 1) / 4;
Were ``1`` not promoted to double, the result in ``q`` would be zero because
``1/4`` is just an integer division, yielding zero. By making one of the
operands a floating-point quantity, the compiler will instruct the FPU to
compute the result. Of course, one could have also written ``1.0`` instead of
``static_cast(double, 1)``, but this is left for the programmer to decide which
style s/he prefers.
.. code-block:: c
/* Force truncation before invoking second sqrt */
double f = sqrt(static_cast(int, 10 * sqrt(3.0 / 4)));
And here, the conversion from ``double`` to ``int`` incurs a (wanted)
truncation of the decimal fraction, that is, rounding down for positive
numbers, and rounding up for negative numbers.
Allowed conversions
-------------------
* Numbers
Conversion between numeric types, such as ``char``, ``short``, ``int``,
``long``, ``long long``, ``intN_t``, both their signed and unsigned variants,
``float`` and ``double``.
* Generic Pointer
Conversion from ``type *`` to and from ``void *``. (Where type may very
well be a type with further indirection.)
* Generic Pointer (const)
Conversion from ``const type *`` to and from ``const void *``.
Limitations
-----------
Because the implementation of our ``static_cast`` involves a C99 compound
literals and those are not constant expressions, ``static_cast`` cannot be used
in such contexts. (Cf. `GCC issue 105510
<https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105510#c3>`_).
.. code-block:: c
static const int a = static_cast(int, 1U);
Furthermore, because an implicit assignment is used in the implementation, it
can trigger `-Wsign-conversion` warnings.
``const_cast``
==============
const_cast allows to add or remove “const” qualifiers from the
type a pointer is pointing to. Due to technical limitations, it
could not be implemented to support arbitrary indirection.
Instead, const_cast comes in three variants, to be used for
indirection levels of 1 to 3:
* ``const_cast1(type *, expr)`` with ``typeof(expr) = type *``.
(Similarly for any combinations of const.)
* ``const_cast2(type **, expr)`` with ``typeof(expr) = type **`` (and all
combinations of const in all possible locations).
* ``const_cast3(type ***, expr)`` with ``typeof(expr) = type ***`` (and all
combinations...).
As indirection levels above 3 are really unlikely[#f3], having only these three
type-checking cast macros was deemed sufficient. The only place where libHX
even uses a level‑3 indirection is in the option parser.
.. [#t3] See “Three Star Programmer”
Conversion is permitted when expression and target type are from the table.
It is currently not possible to use const_cast1/2/3 on pointers to structures
whose member structure is unknown.
|