1. Why language extensions
For example C++ knows native syntactically
extern "C"
It means that the following definition produces a linker label in C manner.
This designation is necessary to mix C and C++ compiled sources, to inform the
C++ compiler that a C-like label should be built. This extern "C"
designation
is necessary in header files which are included from the C implementation file
for C compilation, because the forward declaration should be known, and the same (!)
header file should be included from the C++ sources for the same reason.
But only the C++ compiler knows this extern "C"
designation.
The C compiler forces an compiler error.
For this reason usually headerfiles for C and C++ compilation are written often in the following manner:
# ifdef __cplusplus extern "C" { #endif //... now for all extern declarations extern "C" is valid //... but for all, it is much more # ifdef __cplusplus } #endif
This is the block variante for extern "C" { …. }
, but this seems to be not clearly.
It is very more simple to write:
extern_C MyType myData; //for statically data extern_C void myOperation(...); //for a C-function prototype
It is the same usage as extern
known in C and C++ but with the additional hint
extern "C"
for C++.
Note that the designation with extern
is possible but not necessary for function
prototypes in C, but the extern "C"
is necessary for C++.
The usage of extern_C
harmonizes C and C++. It is simple and clearly.
Unfortunately the creators of the C++ language haven’t this idea.
Other examples for nice to have language extensions for C and C++ compilation are
the recommended usage of static_cast<MyType*>(ref)
in C++
and the adequate error prone reinterpret_cast<MyType*>(ref)
(which should be checked
and secured in runtime). If the same sources are compiled with C and C++ for
different platforms and applications only the C-cast ((MyType*)ref)
seems to be
able to use. But this is fatal if a part defined for C-usage needs necessarily
a static_cast
inside a C++ compilation because the address should be tuned (necessarily!).
The usage of the simple C cast forces a warning often in C++ compilation, warrantable!
For this reason a STATIC_CAST(Type, OBJ)
is offered and a C_CAST(Type, OBJ)
is offered
for the forced, unchecked or reinterpret cast may or should be checked in its
programming environment before execution. It is the reinterpret_cast
for {c++}.
The so named language extensions are define-macros. They are not language extensions
in syntactically meaning but offered macros for common usage if <applstdef_emC.h>
respectively <compl_adaption.h>
is included.
Most of them are defined in the included <emC/Base/types_def_common.h>
2. extern vs. extern_C or extern_CCpp
-
extern_C
isextern "C"
for C++ andextern
for C. It is the designation for declaration of external data or function prototypes which are compiled in C. The defined things has not a additional designation about the type and some modifier which is used for ore safety of the C++ linker. It is in C manner. -
extern_CCpp
: It means that theextern "C"
designation should only be used if the defining source is compiled in C, not using the C++-compiler. The compiler switchDEF_CPP_COMPILE
should be set for all, then all sources which implements this definition have to be compiled as C++. Hence the labels are C++-like labels and the linker check features of C++ are used. It is more safe. -
.. If a source is compiled with C though
DEF_CPP_COMPILE
is set, a linker error occurs. If the source have to be compiled with C for any reason, then useextern_C
for that.//in emC/Base/types_def_common.h: #ifdef __cplusplus #ifdef DEF_CPP_COMPILE //If the apropriate C-sources are compiled as C++ /**C-Sources are compiled with C++, C++ linker label is desired.*/ #define extern_CCpp extern #else //If all C-Sources are compiled with C /**C-Sources are compiled with C*/ #define extern_CCpp extern "C" #endif #define extern_C extern "C" #else #define extern_C extern #define extern_CCpp extern #endif
3. cast compatible for C and C++
-
FORCED_CAST(TYPE, OBJ)
: It is the forced cast, defined asreinterpret_cast<TYPE)(OBJ)
for C++, for C it is the known(TYPE)(OBJ)
.TYPE
is often a reference type andOBJ
is a reference then, for exampleCASTforce_emC(MyType const*, refToBasetype)
. Use this variant of cast only if it is necessary and if the cast is checked and secured. -
C_CAST(TYPE,OBJ)
It is the same asFORCED_CAST
. -
STATIC_CAST(TYPE, OBJ)
: It is the compiler checkedstatic_cast<TYPE>(OBJ)
for C++. This cast is unconditionally necessary to cast between inherited types. Thestatic_cast
in C++ does an address adjustment! It may be that the designationstatic
which is the most used word in C and C++ is not enough clearly in any case! -
The
dynamic_cast<TYPE>(OBJ)
can only be used in C++ sources, hence it is not defined in another way.
4. inline vs. INLINE_emC
The C99 standard should define the usage of inline
as it is known for C++ for C too. Microsoft Visual Studio does not support inline
for C compilation till its version 2013, from version 2015 it is ok.
But already the gcc compiler has its special features, see https://gcc.gnu.org/onlinedocs/gcc/Inline.html. A simple inline
does not work for C compilation, it forces linker errors because it is not accepted. The user should write
static inline int myRoutine ( int x) { return x+1; }
It needs the static
additionally. This may be comprehensible because an access to the routine from another compilation unit needs the linker label (?). static
defines biunique that this routine is only defined for this compilation unit. But why does it run for C++.
If a C-compiler does not support inline
a proper replacement is:
static int myRoutine ( int x) { return x+1; }
It uses static
instead inline
. Using static
it is not a problem that the body is defined in the header, because any compilation unit has its own implementation, not seen as linker label. From the view of compiling it is okay. From the view of code efficiens: The optimizing compiler can expand the body as inline and for some compiler it is also really done. It means static
has the same effect as inline
. Well good.
But what is with source compatibility. The only one way to work is: Do not use inline
for header, which may be included from a C compiler. Instead use a macro which can be adapted.
This macro is INLINE_emC
. It is defined for the gcc compiler as
#ifdef __cplusplus #define INLINE_emC inline #else //See https://gcc.gnu.org/onlinedocs/gcc/Inline.html: #define INLINE_emC static inline #endif
Inline routines should be written in user sources as
INLINE_emC int myRoutine ( int x) { return x+1; }
5. Definitions for packed string constants
If the target need a String (char const*
) as literal in its memory which is byte-packed, because it should be evaluated from another, byte oriented processor, and the given processor is 16- or 32-bit word oriented, some macros can be used. Working with this macros is not nice, but it is a possibility to store immediately packed string. This macro to build one uint32
value with 4 character packes is defined in emC/Base/types_def_common.h
and hence general present.
An guaranteed packed String can be defined platform independent writing:
uint32 myPackedString[2] = {CHAR4_emC('i','d','e','n'),CHAR4_emC('t',0,0,0) };
The String is stored in little endian. The maybe transferred data can be read only by a 8-bit-machine (PC) with casting the memory-position (of this received data) with char const*
:
6. Some attributes on variable and operations
The gcc and some embedded compiler allows #pragma
to attribute some variable declarations and functions.
This is a common approach, but different implemented by several compilers.
Some macros which are defined proper in the compl_adaption.h
can help:
-
MAYBE_UNUSED_emC
before a variable: To mark variables which are calculated but not used in any kind. Sometimes they may be part of an assertions which is switched off, But also sometimes only for information on debugging. This is a pragma for gcc to prevent a warning. In visual studio not regarded. -
USED_emC
the opposite, the variable should be used anytime (necessary?) -
GNU_PACKED
after a struct definiton to force pack -
RAMFUNC_emC
before an operation: It is an attribute before a function definition to determine that the function should be placed in a section which is linked to a RAM location but load into the FLASH memory. This section must be copied on startup to run successfully. It is a designation for embedded hardware with lesser but fast RAM.
7. Helpful macros
-
ARRAYLEN_emC(Array)
it calculates the number of elements in a defined array. It is implemented with#define ARRAYLEN_emC(ARRAY) (uint)(sizeof(ARRAY) / sizeof((ARRAY)[0]))
-
OFFSET_IN_STRUCT
returns the offset of an element given by name in a struct given by type:#define OFFSET_IN_STRUCT(TYPE, FIELD) ((int)(intptr_t)&(((TYPE*)0)->FIELD))
-
SIZEOF_IN_STRUCT
returns the size of an element given by name in a struct given by type:#define SIZEOF_IN_STRUCT(TYPE, FIELD) ((int)(sizeof((TYPE*)0)->FIELD))
-
NNAN(value, valueinstead, check)
This is a possibility to calculate with a value which is before calculated as Not a number (float, double). -
ifNNAN(value, check)
adequate, check a value, create an if statment
8. little and big endian
Usual special endian values for communication are stored as normal int, float, int32_t values, but there content are swapped by the known functions htons etc. (winsock.h, Posix). What is faulty: The designated data type is faulty. A normal access to this int, float etc. value is faulty, but it cannot be detected by the compiler. What is faulty too: If the conversion routine is used twice by accident, the compiler cannot detect it.
The better way is a special data type:
//in emC/OSAL/os_endian.h /**All big-endian-types are define as struct, don't access it immediately. */ typedef struct int64BigEndian_t { int32_t hiBigEndian__; int32_t loBigEndian__; } GNU_PACKED int64BigEndian; typedef struct uint64BigEndian_t { uint32 hiBigEndian__; uint32_t loBigEndian__; } GNU_PACKED uint64BigEndian; typedef struct int32BigEndian_t { int32_t loBigEndian__; } int32BigEndian; typedef struct uint32BigEndian_t { uint32_t loBigEndian__; } uint32BigEndian; typedef struct int16BigEndian_t { int16_t loBigEndian__; } int16BigEndian; typedef struct floatBigEndian_t { int32_t floatBigEndian__; } floatBigEndian; typedef struct doubleBigEndian_t { int32_t hiBigEndian__; int32_t loBigEndian__; } GNU_PACKED doubleBigEndian; typedef struct ptrBigEndian_t { void* ptrBigEndian__; } ptrBigEndian;
The access to this struct content are done only with special conversion routines
which does not need more calculation time then the standard hton
etc. But they are
more save, the compiler checks all:
Note: The GNU_PACKED
is a maybe empty macro for the keyword for the alignment control
for packing the data, which is compiler specific.
//in emC/OSAL/os_endian.h #if defined(OSAL_LITTLEENDIAN) || defined(OSAL_MEMWORDBOUND) /**Use methods, because only 1 access to the memory should be done. */ int64_t getInt64BigEndian ( int64BigEndian const* addr); // etc.
-
OSAL_BIGENDIAN
: It is defined in the<compl_adaption.h>
specific for the plattform. It means that the platform is native big endian. Hence the simple replacement is used. -
OSAL_LITTLEENDIAN
: It is defined in the<compl_adaption.h>
specific for the plattform. It means that the platform is native little endian. Hence all big endian types are typedef which can only be accessed via dedicated
The implementation of this routines regard the memory organization (may be 16- oder 32-bit per address step) too.