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

Confusing definition

C:\Nordic Semiconductor\nrf51_sdk_v4_4_1_31827\nrf51822\Include\ble\softdevice\ble_gatts.h


typedef struct {
  uint16_t     handle;    /**< Attribute Handle. */
  uint8_t       op;          /**< Type of write operation, see @ref BLE_GATTS_OPS. */
  ble_gatts_attr_context_t    context;     /**< Attribute Context. */
  uint16_t      offset;      /**< Offset for the write operation. */
  uint16_t      len;          /**< Length of the incoming data. */
  uint8_t       data[1];    /**< Incoming data, variable length. */
} ble_gatts_evt_write_t;

My question is how to interpret the element data[1]? Is it just uint8_t *data(C-wise it is)? If so why is it defined in such a way? Especially with the size of the data array as one with the following comment about its variable length which all together makes this definition very confusing.

Thanks.

Parents
  • The point of this declaration is that the array is actually located here, and it's not just a pointer to another area. In C99, you would write this as data[], but this is not legal in C89, hence this [1].

    Having it like this

    
    struct
    {
        uint8_t * data;
    } ble_gatts_evt_write_t;
    
    

    would imply there is just a pointer to some other memory area in the actual event structure, while this

    
    struct
    {
        uint8_t data[]; // or data[1] in C89
    } ble_gatts_evt_write_t;
    
    

    shows that the entire data is actually located in this memory area.

    Initially, it was a requirement for the softdevice headers to be C89 compliant, and even though we've abandoned this, some things like this still lingers in the code. However, when using this buffer, these two declarations are mostly equivalent, although it may matter in case you need to copy the structure.

    It seems to me that this blog post explains the difference between a pointer and an array in a good way, if the difference is still unclear.

Reply
  • The point of this declaration is that the array is actually located here, and it's not just a pointer to another area. In C99, you would write this as data[], but this is not legal in C89, hence this [1].

    Having it like this

    
    struct
    {
        uint8_t * data;
    } ble_gatts_evt_write_t;
    
    

    would imply there is just a pointer to some other memory area in the actual event structure, while this

    
    struct
    {
        uint8_t data[]; // or data[1] in C89
    } ble_gatts_evt_write_t;
    
    

    shows that the entire data is actually located in this memory area.

    Initially, it was a requirement for the softdevice headers to be C89 compliant, and even though we've abandoned this, some things like this still lingers in the code. However, when using this buffer, these two declarations are mostly equivalent, although it may matter in case you need to copy the structure.

    It seems to me that this blog post explains the difference between a pointer and an array in a good way, if the difference is still unclear.

Children
  • Sorry Ole, I'm not satisfied. some_t data[X] allocates memory for X elements of some_t, in our case uint8_t data[1] allocates exactly one byte. Yet the comment says variable length! Is this not confusing? One needs to choose array of predefine length of exact or max_len size or void pointer which application will cast to the necessary type. Once again I'm finding existing typedef including its comment confusing: If one has more then one write event and they do have different length of writing data then more then one typedef will be required.

  • I'm sorry, but I can't really help that you find it confusing, but this is perfectly valid C, and the use case is fine: to make it clear that the array is placed inside the struct, and not just a pointer to somewhere else.

    I'd again like to recommend having a look at the blog post I linked. Also remember that this struct is inside a ble_evt_t, and where it is primarily used, in BLE_STACK_HANDLER_INIT(), space is allocated like this: static uint32_t EVT_BUFFER[CEIL_DIV(sizeof(ble_evt_t) + (MTU_SIZE), sizeof(uint32_t))]; This makes sure that sufficient space will actually be allocated, since the MTU_SIZE is added to the sizeof(ble_evt_t), exactly to accommodate the variable size arrays that are part of several events.

  • "to make it clear that the array is placed inside the struct, and not just a pointer to somewhere else." Understood. To achieve that one has to allocate the maximum possible length and then length will become variable from 1 to max. If such maximum length is defined as one how come it possible be variable as the comment says?! Also this approach means that EACH write event has to have its own type and if so how this type is defined inside the common library header?

    I'm afraid that this dialog is going to nowhere and if so it is probably the best to stop it as is unresolved...

  • I'm sorry for the delay here, but we've been busy with internal training lately.

    I guess you're right that another option instead of using the [1] would have been to use [BLE_L2CAP_MTU_DEF], but in my opinion whether that's any clearer is mostly a matter of taste, when most of this array may very well be uninitialized memory.

    This array will actually be variable length, since it will contain whatever was in the write event coming over the air. From the S110 side, you have no control over this, it's only limited by the peer device and the Bluetooth Spec.

    I'm a little unsure why you found my answer worthy a down-vote, though, as I seriously tried to explain why this was there and what it means. I'm sorry if I came off rude, but I can very well see why this seems confusing. What I can't quite understand is how you can insist that I and my explanation is wrong.

  • "in my opinion whether that's any clearer is mostly a matter of taste, when most of this array may very well be uninitialized memory." I'm always applying the rule: clearly define the object and use it's definition. In other words in my application expects a message it needs to know how to interpret it. So typedef enum { MON, TUE, WED, THU, FRI, SAT, SUN } my_app_t is much better than uint8_t week_day; And when the message structure is typedef struct { my_app_t begin; my_app_t end; } my_msg_t; I'd verify that len is == sizeof(my_msg_t) and then will use it as data->begin and data->end and not data[0] and data[1] and getting warning about data[1]... BTW Warnings should be strictly prohibited - compiler doesn't understand your intent and therefore such code is not portable even if it is correct for particular case.

    Isn't if clear and very readable?!

    uint8_t

Related