Data type differences - the key
differences in this context are summarized and highlighted in Table 2-2.
Table 2-2 Data Type Key
Differences
Type |
C28 |
CLA |
C29 |
ARM |
char |
16 |
16 |
8 |
8 |
short |
16 |
int |
16 |
32 |
32 |
32 |
long |
32 |
long long (COFF) |
64 |
32 |
N/A
|
64 |
long long (EABI) |
64 |
float |
32 |
double (COFF) |
32 |
32 |
N/A
|
64 |
double (EABI) |
64 |
long double (COFF) |
64 |
32 |
N/A
|
64 |
long double (EABI) |
64 |
Pointers |
32 |
16 |
32 |
32 |
The
user needs to pay careful attention to data types:
- int (C28 16-bit vs C29 32-bit) -
If the user's code has occurrences of "int", the following is recommended for
migration:
- Change all occurrences of
"int" in user code to a fixed-width type int16_t. This ensures nothing
in the code breaks, and original data widths are retained.
- The C29 compiler
has a tool called c29clang-tidy which checks for occurrences of
"int" and "unsigned int", which is discussed here (under
c29migration-c28-int-decls)
- Ideal scenario - user
code does not contain any occurrences of "int", instead only contains
portable fixed-width types like int16_t, int32_t. In this case, the code
is portable without any changes.
- char (C28 16-bit vs C29 8-bit) -
If the user's code has occurrences of "char" or "int8_t" or "uint8_t":
- At the outset, changing
all occurrences of "char" to "int16_t" may seem like the migration
approach, since it is retaining the bit-width present in the user's
original code. However, this can lead to unexpected build-time issues,
or worse, hard to detect run-time failures. This is because it is not
legal for pointers of different types to alias. Char, however, is
special and may alias with other pointer types. In other words, any
object can be accessed through a pointer to char. So if you now change
char to int16_t, you will violate this "special privilege" that char
alone has.
- Therefore, the
recommended approach to migration is to make no changes to the code, or
preferably, change all occurrences of "char" in user code to "int8_t".
Even this approach has some problematic scenarios:
- If user code
contains "char" occurrences but really has 16-bit dependency,
which needs to be changed to int16_t. For example, assigned data
that is out of 8-bit range and needs 16-bit range. The C29
compiler has a tool called c29clang-tidy that checks for
operations on "char" typed expressions that would be out of
range for an 8-bit type, which is discussed here (under
c29migration-c28-char-range)
- If user code
contains pointers to "char" and associated offsets, or
arithmetic on such pointers. The C29 compiler has a tool called
c29clang-tidy that checks for pointer arithmetic on
char/int-based pointers, whose bit stride changes between C28 to
C29, which is discussed here (under c29migration-c28-types
and c29migration-c28-suspicious-dereference).
Note: Note that
Pointer aliasing is not an issue with changing "char" to
"int8_t".
- Differences in sizeof() due to
the data type differences - This is summarized in the table, and leads to
differences in behavior when using standard library function calls:
|
sizeof(char) |
sizeof(short) |
sizeof(int) |
sizeof(long) |
C28 |
1 |
1 |
1 |
2 |
C29 |
1 |
2 |
4 |
4 |
- If a hardcoded size is
used, the C29 compiler has a tool called c29clang-tidy which checks for
library calls without a sizeof() expression, which is discussed here (under c29migration-c28-stdlib).
- Functions which work on
values byte-by-byte, like memset, do not port from C28 to C29. If memset
is used with sizeof(int), sizeof(int16_t), sizeof(char), or
sizeof(int8_t), the behavior is different between C28 and C29. Consider
the following example with memset. Some of the functions that are
affected are memset, memccpy, memchr, strncmp.
memset(buf,5,2 * sizeof(char));
Byte address offset at buf: 0 8 16 24 32 40 48 56 64
C28: 5 5
C29: 5 5
memset(buf,5,2 * sizeof(short));
Byte address offset at buf: 0 8 16 24 32 40 48 56 64
C28: 5 5
C29: 5 5 5 5
memset(buf,5,2 * sizeof(int));
Byte address offset at buf: 0 8 16 24 32 40 48 56 64
C28: 5 5
C29: 5 5 5 5 5 5 5 5
Differences in behavior
occur with other functions like memcpy as well, even if they don't operate byte wise.
Consider the following example with memcpy.
memcpy(dst,src,4 * sizeof(char));
Byte address offset at dst: 0 8 16 24 32 40 48 56 64
C28: 1 2 3 4 5 6 7 8
C29: 1 2 3 4
Note how with memcpy, in order to obtain
the same behavior on both C28 and C29, CHAR_BIT (defined in limits.h) can be
used.
#if(CHAR_BIT == 16)
memcpy(dst,src,4 * sizeof(char));
#endif
#if(CHAR_BIT == 8)
memcpy(dst,src,4 * sizeof(char) * 2);
#endif
Byte address offset at dst: 0 8 16 24 32 40 48 56 64
C28: 1 2 3 4 5 6 7 8
C29: 1 2 3 4 5 6 7 8
Note: The portability of fixed-width types
is possible with the use of C header stdint.h.