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 is extern "C" for C++ and extern 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 the extern "C" designation should only be used if the defining source is compiled in C, not using the C++-compiler. The compiler switch DEF_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 use extern_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 as reinterpret_cast<TYPE)(OBJ) for C++, for C it is the known (TYPE)(OBJ). TYPE is often a reference type and OBJ is a reference then, for example CASTforce_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 as FORCED_CAST.

  • STATIC_CAST(TYPE, OBJ): It is the compiler checked static_cast<TYPE>(OBJ) for C++. This cast is unconditionally necessary to cast between inherited types. The static_cast in C++ does an address adjustment! It may be that the designation static 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.