springRoll
springRoll

Reputation: 344

Continuous memory allocation with different data type in C?

I'm trying to compose a string (char array exactly) containing a fixed 14 starting characters and ending with varying content. The varying bit contains 2 floats and 1 32-bit integer that's to be individually treated as 4 1-byte characters in the array separated by commas. It can be illustrated by the following piece of code, which doesn't compile for some obvious reasons (*char can't assign to *float). So, what can I do to get around it?

char *const comStr = "AT+UCAST:0000=0760,0020,0001\r"; // command string

float *pressure;
float *temperature;
uint32_t *timeStamp;

pressure = comStr + 14; // pressure in the address following the '=' in command string

temperature = comStr + 18; // temperature in the address following the 1st ',' in command string

timeStamp = comStr + 22; // time stamp in the address following the 2nd ',' in command string

I have an unclear memory about something like struct and union in the C language which reserves strictly the memory allocation order in which the variables are defined within the "structure". Maybe something like this:

typedef struct
{
  char[14] command;
  float *pressure;
  char comma1;
  float *temperature;
  char comma2;
  uint32_t *time_stamp;
  char CR;
}comStr;

Does this structure guarantee that comStr-> command[15] gives me the first/last byte (depends on the endian) of *pressure? Or is there any other special structure do the trick hiding from me?

(Note: comStr-> command[15] isn't going to be evaluated in future code, so exceeding index boundary is not a concern here. The only important thing here is just whether the memory is allocated continuously so that a hardware fetch lasting for 29 bytes starting from the memory address (comStr-> command) gives me exactly the string I want).

p.s. As I am writing this, I came up with an idea. Can I possibly just use memcpy() for the purpose ;) memcpy has parameters of void* type, hopefully it works! I am going to try it now! All hail stackOverflow anyway!

EDIT: I should have made myself clearer, sorry for any misleading and misunderstanding! The character array I want to construct is to be sent through UART byte by byte. To do this, a DMA system is to be used to transfer the array to the transmit buffer byte by byte automatically if the character array's starting memory address and length are given to the DMA system. So the character array must to be stored continuously in the memory. I hope this makes the question clearer.

Upvotes: 0

Views: 1539

Answers (3)

springRoll
springRoll

Reputation: 344

At last, I found an easy way round but I don't know if there is any drawback for this method. I did:

char commandStr[27];
char *commandHeader = "AT+UCAST:0000=";
float pressure = 760.0;
float temperature = 20.0;
uint32_t timeStamp = 0;

memcpy(commandStr, commandHeader, 14);
commandStr[26] = '\r';

memcpy((void*)(comStr+14), (void*)(&pressure), 4);
memcpy((void*)(comStr+18), (void*)(&temperature), 4);
memcpy((void*)(comStr+22), (void*)(&timeStamp), 4);

Does this code have any security issues or performance issues or whatever?

Upvotes: 0

Jonathan Leffler
Jonathan Leffler

Reputation: 755064

This proposed structure:

typedef struct
{
  char[14] command;
  float *pressure;
  char comma;
  float *temperature;
  char comma;
  uint32_t *time_stamp;
  char CR;
}comStr;

Is not going to help you with your requirement:

The only important thing here is just whether the memory is allocated continuously so that a hardware fetch lasting for 29 bytes starting from the memory address (comStr->command) gives me exactly the string I want.

Note you can't have two members with the same name; you'd need to use comma1 and comma2 for example. Also, the array dimension is in the wrong place.

One problem is that there will be padding bytes within the structure.

Another problem is that the pointers will be holding addresses of something outside the structure (since there is nothing valid inside the structure for them to point at).

It is not clear what you're after. Only a very limited range of floating point values can be represented by 4 bytes in a string. If you're after binary data I/O, then you can drop the pointers and the commas:

typedef struct
{
  char     command[14];
  float    pressure;
  float    temperature;
  uint32_t time_stamp;
}comStr;

If you want the commas present, then you're going to have to work harder:

typedef struct
{
  char     command[14];
  char     pressure[4];
  char     comma1;
  char     temperature[4];
  char     comma2;
  char     time_stamp[4];
  char     CR;
} comStr;

You will have to load the data carefully:

struct comStr com;
float         pressure = ...;
float         temperature = ...;
uint32_t      time_stamp = ...;

assert(sizeof(float) == 4);
...
memmove(&com.pressure, &pressure, sizeof(pressure));
memmove(&com.temperature, &temperature, sizeof(temperature));
memmove(&com.time_stamp, &time_stamp, sizeof(time_stamp));

You have to unpack with a similar set of memory copies. Note that you won't be able to use simple string manipulation on the structure; there could be zero bytes in any or all of the pressure, temperature and time_stamp sections of the structure.


Structure padding

#include <stddef.h>
#include <stdio.h>
#include <stdint.h>

typedef struct
{
  char      command[14];
  float    *pressure;
  char      comma1;
  float    *temperature;
  char      comma2;
  uint32_t *time_stamp;
  char      CR;
} comStr;

int main(void)
{
    static const struct
    {
        char    *name;
        size_t   offset;
    } offsets[] =
    {
        { "command",     offsetof(comStr, command)     },
        { "pressure",    offsetof(comStr, pressure)    },
        { "comma1",      offsetof(comStr, comma1)      },
        { "temperature", offsetof(comStr, temperature) },
        { "comma2",      offsetof(comStr, comma2)      },
        { "time_stamp",  offsetof(comStr, time_stamp)  },
        { "CR",          offsetof(comStr, CR)          },
    };
    enum { NUM_OFFSETS = sizeof(offsets)/sizeof(offsets[0]) };

    printf("Size of comStr = %zu\n", sizeof(comStr));
    for (int i = 0; i < NUM_OFFSETS; i++)
        printf("%-12s  %2zu\n", offsets[i].name, offsets[i].offset);

    return 0;
}

Output on Mac OS X:

Size of comStr = 64
command        0
pressure      16
comma1        24
temperature   32
comma2        40
time_stamp    48
CR            56

Note how large the structure is on a 64-bit machine. Pointers are 8-bytes each and are 8-byte aligned.

Upvotes: 1

user93353
user93353

Reputation: 14049

Various issues to be a covered in your question. I'll take a shot at some of those issues.

  • The order of members in a structure is guaranteed to be the same as order you have declared them. But there is a different issue here - padding. Check this -http://c-faq.com/struct/padding.html and follow other links/questions there
  • Next thing is that you are mistaken in thinking that something like "125" is an integer or something like "1.25" is a float - it's not - it's a string. i.e.

    char * p = "125";
    

    p[0] will not contain 0. It will contain '0' - if the encoding is ASCII, then this will be 48. i.e. p[0] will contain 48 & not 0. p[1] will contain 49 & p[2] will contain 52. It will be something similar for float.

The opposite will also happen. i.e. if you have at an address and you treat it as a char array - the char array will not contain the float you think it will. Try this program to see this

#include <stdio.h>

struct A
{
    char c[4];
    float * p;
    int i;
};

int main()
{
    float x = 1.25;
    struct A a;
    a.p = &x;
    a.i = 0; // to make sure the 'presumed' string starting at p gets null terminate after the float
    printf("%s\n", &a.c[4]); 
}

For me, it prints "╪·↓". And this has nothing to do with endianness.

  • Another thing you need to remember, while assigning values to your structure object - you need to remember that comStr.pressure & comStr.temperature are pointers. You cannot assign values to them directly. You need to either give them the address of an existing float or allocate memory dynamically to which they can point to.

Also are you trying to create the char array or to parse the char array which already exists. If you are trying to create it, a better way to do this will be to use snprintf to do what you want. snprintf uses format specifiers similar to printf but prints to a char array. You can create your char array that way. A bigger question remains - what do you plan to do with this char array you create - that will determine if endianness is relevant for you.

If you are trying to read from the char array you have been given and trying to split into floats and commas and whatever, then one way to do this will be sscanf but may be difficult for your particular string format.

Upvotes: 1

Related Questions