Skip to main content

IAR Embedded Workbench for RX 5.20

Selecting data types

In this section:

For efficient treatment of data, you should consider the data types used and the most efficient placement of the variables.

Using efficient data types

The data types you use should be considered carefully, because this can have a large impact on code size and code speed.

  • Use auto variables. Stack accesses are cheaper than global accesses, and many auto variables will end up in registers, making execution very fast.

  • Use unsigned integer types where possible, unless your application really requires signed values. Many loop optimizations will work much better with unsigned loop variables.

  • Try to avoid 64-bit data types, such as 64-bit double and longlong.

  • Bitfields with sizes other than 1 bit should be avoided because they will result in inefficient code compared to bit operations.

  • Use signed or unsigned int for array indexing.

  • Using floating-point types without using the built-in floating-point unit is very inefficient, both in terms of code size and execution speed.

  • Declaring a pointer to const data tells the calling function that the data pointed to will not change, which opens for better optimizations.

For information about representation of supported data types, pointers, and structure types, see the chapter Data representation.

Floating-point types

Using floating-point types on a microprocessor without a math coprocessor is inefficient, both in terms of code size and execution speed. Therefore, you should consider replacing code that uses floating-point operations with code that uses integers, because these are more efficient.

The compiler supports two floating-point formats—32 and 64 bits. The 32-bit floating-point type float is more efficient in terms of code size and execution speed. The 64-bit format double supports higher precision and larger numbers.

In the compiler, the floating-point type float always uses the 32-bit format. The format used by the double floating-point type depends on the setting of the ‑‑double compiler option.

Unless the application requires the extra precision that 64-bit floating-point numbers give, we recommend using 32-bit floating-point numbers instead.

By default, a floating-point constant in the source code is treated as being of the type double. This can cause innocent-looking expressions to be evaluated in double precision. In the example below a is converted from a float to a double, the double constant 1.0 is added and the result is converted back to a float:

double Test(float a)
{
   return a + 1.0;
}

To treat a floating-point constant as a float rather than as a double, add the suffix f to it, for example:

double Test(float a)
{
   return a + 1.0f;
}

For more information about floating-point types, see Basic data types—floating-point types.

Casting a floating-point value to an integer

If you want the result of casting a float to an int to be a rounded value instead of a truncated value, use the intrinsic function __ROUND to insert a ROUND instruction directly into the code. See __ROUND.

Alignment of elements in a structure

The RX microcontroller requires that when accessing data in memory, the data must be aligned. Each element in a structure must be aligned according to its specified type requirements. This means that the compiler might need to insert pad bytes to keep the alignment correct.

There are situations when this can be a problem:

  • There are external demands, for example, network communication protocols are usually specified in terms of data types with no padding in between

  • You need to save data memory.

For information about alignment requirements, see Alignment.

Use the #pragma pack directive or the __packed data type attribute for a tighter layout of the structure. The drawback is that each access to an unaligned element in the structure will use more code.

Alternatively, write your own customized functions for packing and unpacking structures. This is a more portable way, which will not produce any more code apart from your functions. The drawback is the need for two views on the structure data—packed and unpacked.

For more information about the #pragma pack directive, see pack.

Anonymous structs and unions

When a structure or union is declared without a name, it becomes anonymous. The effect is that its members will only be seen in the surrounding scope.

Example

In this example, the members in the anonymous union can be accessed, in function F, without explicitly specifying the union name:

struct S
{
  char mTag;
  union
  {
    long mL;
    float mF;
  };
} St;

void F(void)
{
  St.mL = 5;
}

The member names must be unique in the surrounding scope. Having an anonymous struct or union at file scope, as a global, external, or static variable is also allowed. This could for instance be used for declaring I/O registers, as in this example:

__no_init volatile
union
{
  unsigned char IOPORT;
  struct
  {
    unsigned char Way: 1;
    unsigned char Out: 1;
  };
} @ 8;

/* The variables are used here. */
void Test(void)
{
  IOPORT = 0;
  Way = 1;
  Out = 1;
}

This declares an I/O register byte IOPORT at address 8. The I/O register has 2 bits declared, Way and Out. Note that both the inner structure and the outer union are anonymous.

Anonymous structures and unions are implemented in terms of objects named after the first field, with a prefix _A_ to place the name in the implementation part of the namespace. In this example, the anonymous union will be implemented through an object named _A_IOPORT.