简体   繁体   中英

Does it make sense to type cast literals?

So I am working on memory constrained embedded system and want to save as much bytes as possible. If, in my code if I have statements like:

b = a << 1;

or

b += 1;

where a and b are uint8_t . Is it beneficial to type cast the literal to same type or is it done by compiler:

b = a << (uint8_t) 1;

b += (uint8_t) 1;

is it beneficial to tye cast the literal to same type or is it done by compiler:

You are at the mercy of the compiler with respect to how it stores the values of constants in binaries it creates. There is no special reason to think that casts such as you propose will change the representation used, as they nominally express runtime conversions (from int , in this case). Moreover, and without going into details, C specifies that the operands of arithmetic operations will be promoted to a type at least as wide as int for the purpose of computing the results of arithmetic operations. A compiler might plausibly pre-compute such a conversion, effectively nullifying your casts altogether.

Your casts might actually be worse, however, if they happen to prevent the compiler from recognizing opportunities to avoid storing constant values at all. For example, speculatively, if your target CPU has a specific instruction to increment the value of a register by exactly 1, then it might use that to implement

b += 1;

... but fail to recognize that it can do the same thing with

b += (uint8_t) 1;

... on account of (uint8_t) 1 being a non-primary expression.

Use casts sparingly, and only as necessary to describe the semantics of your program. Rely on your compiler to do a good job, and if it doesn't then look for a better one. Compilers for embedded environments can usually be relied upon to understand the importance of minimizing code size, and even compilers for general-purpose platforms often have options to request optimization for minimum code size.

在这种情况下,这无关紧要,因为在表达式中使用时,任何类型级别低于int整数值都会自动提升为int

In the provided examples the benefit is to maintain sign-agreement rather then type-agreement . Type agreement in this case is defeated in any case by type promotion rules; the literal operand, after being cast to uint8_t will be promoted to unsigned int . Without the cast it would be promoted to int and in some expressions could potentially produce unintended or undefined results (though not in these examples - right-shift for example is ambiguous for a negative signed value).

An arguably better way of maintaining sign-agreement is to use the unsigned literal suffix u :

b = a << 1u ;
b += 1u ;

Literals like in your examples won't likely be stored as separate numeric constants, but will be integrated in the machine code instructions. In your case, the result will be some instruction like "logical left shift register x, 1". The number will then be as large as required for the particular instruction, no matter what the higher level C language says.

These are the kind of optimizations you should leave to the compiler. That goes for integer constants ("literals"), #define ones and usually also for enums.

However, if you have constants used as const variables, you could manually pick the smallest needed type in order to save flash. If you declare something like for example static const int x = 123; then you risk blocking the compiler from using any smaller type than whatever int happens to be.

The types uint_leastn_t from stdint.h were intended to use for memory optimizations. If you write static const uint_least8_t = 123; then the compiler will pick the smallest type available, of at least 8 bytes. (Which most likely will yield the very same result as uint8_t on most systems.)

I did some experimentation on Compiler Explorer .

#include <cstdint>

uint64_t A()
{
    return uint64_t{1} << 4;
}

uint64_t B()
{
    return static_cast<uint64_t>(1) << 4;
}

uint64_t C()
{
    return uint64_t(1) << 4;
}

// uint64_t D()
// {
//     return uint8_t{256}; <--- Compile time error
// }

uint64_t E()
{
    return static_cast<uint8_t>(256);
}

uint64_t F()
{
    return uint8_t(256);
}

Clang 11.0.1 with -std=c++20 -O1 generated the following assembler:

A():                                  # @A()
        mov     eax, 16
        ret
B():                                  # @B()
        mov     eax, 16
        ret
C():                                  # @C()
        mov     eax, 16
        ret
E():                                  # @E()
        xor     eax, eax
        ret
F():                                  # @F()
        xor     eax, eax
        ret

and the following error for D()

<source>:20:20: error: constant expression evaluates to 256 which cannot be narrowed to type 'uint8_t' (aka 'unsigned char') [-Wc++11-narrowing]
    return uint8_t{256};
                   ^~~
<source>:20:20: note: insert an explicit cast to silence this issue
    return uint8_t{256};
                   ^~~
                   static_cast<uint8_t>( )
<source>:20:20: warning: implicit conversion from 'int' to 'uint8_t' (aka 'unsigned char') changes value from 256 to 0 [-Wconstant-conversion]
    return uint8_t{256};
                  ~^~~
1 warning and 1 error generated.
Compiler returned: 1

GCC 10.2 with -std=c++20 -O1 generated the following assembler:

A():
        mov     eax, 16
        ret
B():
        mov     eax, 16
        ret
C():
        mov     eax, 16
        ret
E():
        mov     eax, 0
        ret
F():
        mov     eax, 0
        ret

and the following error for D()

<source>: In function 'uint64_t D()':
<source>:20:23: error: narrowing conversion of '256' from 'int' to 'uint8_t' {aka 'unsigned char'} [-Wnarrowing]
   20 |     return uint8_t{256};
      |                       ^
Compiler returned: 1

Conclusions

  • Worrying about a compiler not recognising a primary expression BEFORE testing is premature optimisation
  • Using {} is superior to () or static_cast because it ensures our number literals are safely within range of our target type.
  • If we WANT to do something that {} doesn't allow, we will be forced to state our intentions to the compiler AND therefore other developers (see clangs compile error note: insert an explicit cast to silence this issue )

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM