This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Can you make some small changes for C++ compatibility.

I had to modify the 0.10 mesh / rf sdk's to integrate them into a C++ environment and have now to do the same again with the latest SDK's due to some coding issues in the supplied FW

All of our code is written in C++ and my mesh application code also gets compiled into a simulation environment, switching to C is a more difficult option than switching to an alternative BT device supplier.

The issue for me since we use the Keil environment is mainly an issue with incomplete parameter specifications, many functions take an array of undefined length as their final parameter, this is not compatible with C++, but the ARM compilers allow it using the --gnu switch.

This however causes some compatiblity issues with other files which check for the definition of __GNUC_ causing conflicts, for example the assert definitions in app_util.h

A simple fix for this in these and simpler definitions would help greatly, make the test for __GNU_C conditional on __CC_ARM not being defined, this would have no impact on users of GCC, but would avoid users of ARM compilers needing to modify the source files when using C++

Better still, avoid the use of undefined length arrays, it is not necessary as defining them as length 1, or better still a pointer would resolve the C++ compatibility, after all you are actually passing a pointer anyway,

  • Thanks, interesting reading but they have not addressed any of the issues at Nordic.

    I'm not asking them to write it in C++, simply to avoid using constructs that do not compile with the ISO/ANSI C++ compiler modes.

    I've written all my embedded code in "C++" for over 20 years and can't understand why anybody would choose to start a new development in "C", but for me it's a bigger issue since I instantiate hundreds of instances of my code in a simulation environment for system level modelling and C++ provides the encapsulation to do that easily and efficiently ( and also produces significantly more efficient object code ).

    The Nordic code is totally incompatible with this technique as it is not possible when using any globals as they cannot be distinguished between multiple instantiations of the device an a model.

    one reason for the increased efficiency of C++ is that writing object oriented code makes you group data into objects that are related and operated on by the same functions thereby providing much better localization of data which greatly simplifies effective address calculation, i.e. the compiler gets to use a single pointer ( this,  generally R0 ) with short immediate offsets for accessing data a lot more often and  that reduces code size and increases execution efficiency significantly.

    Look at any compiled code and the bulk of the instructions are for effective address computation when data is too far apart to be accessed in this manner.

    Frankly the structure of the data in the Nordic code is really badly managed and inefficient but I can live with that provided I can write my code in C++.

  • Hi,

    I am trying to understand the issue here. Doesn't the use of 'extern "C"' throughout the SDK let you build the SDK using the C compiler still using the APIs from C++? Or can it not be bridged because of certain function headers? (Can you show me an example taken from the SDK of a function that is problematic?)

    Regards,
    Terje

  • The use of extern "C" simply tells the compiler not to decorate the names of the functions so that the linker can recognize them, the default for C++ is that all function names are decorated with the types which would prevent linking the files.

    extern "C" is essential but insufficient since there are many instances of incomplete type definitions in the function parameters, what this actually means is that C allows for the last parameter to a function to be an un-dimensioned array, which ultimately gets passed as a pointer.

    C++ does not allow for this and sees it as an incomplete type definition, GCC allows it but the ARM compilers do not.

    There is a switch that can be used with ARMCC, --gnu, to make it work in GCC compatible mode which enables support for this mode, but in doing so it defines __GNU_C as well as __CC_ARM_

    in this mode the code will not compile using ARM compilers without editing the source since there are places in the code, particularly in the error logging, where having both of these defined results in incompatible redefinition of macros.

    in summary there are 2 problems.

    1. The use of arrays of undefined length it bad practice, that's why it was not allowed in C++, arrays are always passed by reference so a pointer is the preferred manner of passing arrays in both C and C++

    2. The assumption that __CC_ARM_ and __GNU_C are mutually exclusive is not valid, in GCC __CC_ARM_ would never be defined, but in ARMCC they will both be defined when using --gnu

    both of these would be trivial for Nordic to fix and would not affect the generated code in GCC or ARMCC, but would avoid the need to edit the nordic SDK when building a C++ application with ARMs compilers

    for example

    /** BLE Service data packets. */
    typedef struct __attribute((packed))
    {
       uint16_t uuid; /**< UUID field. */
       uint8_t data[]; /**< Service data field. */
    } ble_ad_data_service_data_t;

    ..\..\..\..\mesh\core\include\packet.h(142): error:  #70: incomplete type is not allowed

    it would have been better to define it as 

    /** BLE Service data packets. */
    typedef struct __attribute((packed))
    {
       uint16_t uuid; /**< UUID field. */
       uint8_t *data; /**< Service data field. */
    } ble_ad_data_service_data_t;

    this has an identical meaning and is compatible with all compilers in both C and C++ modes.

  • My 2 cents to this.  Do not use __attribute((packed)).  it is GCC extension.  The C language has #pragma pack(x) since the beginning of the C language.  Microsoft added the extension #pragma pack(push,x) back in the 80s.  It is now a pseudo standard supported by all compilers from VCC to OSX Clang and GCC of course and fully compatible with C++.  So the structure should be rewritten as:

    /** BLE Service data packets. */

    #pragma pack(push, 1)
    typedef struct 
    {
       uint16_t uuid; /**< UUID field. */
       uint8_t *data; /**< Service data field. */
    } ble_ad_data_service_data_t;

    #pragma pack(pop)

    if this structure is meant to be variable length it should be

    /** BLE Service data packets. */

    #pragma pack(push, 1)
    typedef struct 
    {
       uint16_t uuid; /**< UUID field. */
       uint8_t data[1]; /**< Service data field. */
    } ble_ad_data_service_data_t;

    #pragma pack(pop)

    Also this kind of macro is consider as bad programming practice, please remove as well.

    #define NRF_SECTION_SET_DEF(_name, _type, _count)                                                   \ 
    /*lint -save -emacro(14, MACRO_REPEAT_FOR*)  */                                                     \ 
    MACRO_REPEAT_FOR(_count, NRF_SECTION_DEF_, _name, _type)                                            \ 
    static nrf_section_t const CONCAT_2(_name, _array)[] =                                              \ 
    {                                                                                                   \ 
        MACRO_REPEAT_FOR(_count, NRF_SECTION_SET_DEF_, _name)                                           \ 
    };                                                                                                  \ 
    /*lint -restore */                                                                                  \ 
    static nrf_section_set_t const _name =                                                              \ 
    {                                                                                                   \ 
        .p_first   = CONCAT_2(_name, _array),                                                           \ 
        .p_last    = CONCAT_2(_name, _array) + ARRAY_SIZE(CONCAT_2(_name, _array)),                     \ 
        .item_size = sizeof(_type),                                                                     \ 
    } 
    

Related