Question

I need to put into a char* some uint32_t and uint16_t numbers. Then I need to get them back from the buffer.

I have read some questions and I've tried to use sprintf to put them into the char* and sscanf get the original numbers again. However, I'm not able to get them correctly.

Here's an example of my code with only 2 numbers. But I need more than 2, that's why I use realloc. Also, I don't know how to use sprintf and sscanf properly with uint16_t

uint32_t gid = 1100;
uint32_t uid = 1000;
char* buffer = NULL;
uint32_t offset = 0;

buffer = realloc(buffer, sizeof(uint32_t));
sprintf(buffer, "%d", gid);
offset += sizeof(uint32_t);

buffer = realloc(buffer, sizeof(uint32_t) + sizeof(buffer));
sprintf(buffer+sizeof(uint32_t), "%d", uid);

uint32_t valorGID;
uint32_t valorUID;

sscanf(buffer, "%d", &valorGID);
buffer += sizeof(uint32_t);
sscanf(buffer, "%d", &valorUID);

printf("ValorGID %d ValorUID %d \n", valorGID, valorUID);

And what I get is

ValorGID 11001000 ValorUID 1000 

What I need to get is

ValorGID 1100 ValorUID 1000

I am new in C, so any help would be appreciated.

Was it helpful?

Solution

buffer = realloc(buffer, sizeof(uint32_t));
sprintf(buffer, "%d", gid);
offset += sizeof(uint32_t);

buffer = realloc(buffer, sizeof(uint32_t) + sizeof(buffer));
sprintf(buffer+sizeof(uint32_t), "%d", uid);

This doesn't really make sense, and will not work as intended except in lucky circumstances.

Let us assume that the usual CHAR_BIT == 8 holds, so sizeof(uint32_t) == 4. Further, let us assume that int is a signed 32-bit integer in two's complement representation without padding bits.

sprintf(buffer, "%d", gid) prints the decimal string representation of the bit-pattern of gid interpreted as an int to buffer. Under the above assumptions, gid is interpreted as a number between -2147483648 and 2147483647 inclusive. Thus the decimal string representation may contain a '-', contains 1 to 10 digits and the 0-terminator, altogether it uses two to twelve bytes. But you have allocated only four bytes, so whenever 999 < gid < 2^32-99 (the signed two's complement interpretation is > 999 or < -99), sprintf writes past the allocated buffer size.

That is undefined behaviour.

It's likely to not crash immediately because allocating four bytes usually gives you a larger chunk of memory effectively (if e.g. malloc always returns 16-byte aligned blocks, the twelve bytes directly behind the allocated four cannot be used by other parts of the programme, but belong to the programme's address space, and writing to them will probably go undetected). But it can easily crash later when the end of the allocated chunk lies on a page boundary.

Also, since you advance the write offset by four bytes for subsequent sprintfs, part of the previous number gets overwritten if the string representation (excluding the 0-termnator) used more than four bytes (while the programme didn't yet crash due to writing to non-allocated memory).

The line

buffer = realloc(buffer, sizeof(uint32_t) + sizeof(buffer));

contains further errors.

  1. buffer = realloc(buffer, new_size); loses the reference to the allocated memory and causes a leak if realloc fails. Use a temporary and check for success

    char *temp = realloc(buffer, new_size);
    if (temp == NULL) {
        /* reallocation failed, recover or cleanup */
        free(buffer);
        exit(EXIT_FAILURE);
    }
    /* it worked */
    buffer = temp;
    /* temp = NULL; or let temp go out of scope */
    
  2. The new size sizeof(uint32_t) + sizeof(buffer) of the new allocation is always the same, sizeof(uint32_t) + sizeof(char*). That's typically eight or twelve bytes, so it doesn't take many numbers to write outside the allocated area and cause a crash or memory corruption (which may cause a crash much later).

You must keep track of the number of bytes allocated to buffer and use that to calculate the new size. There is no (portable¹) way to determine the size of the allocated memory block from the pointer to its start.


Now the question is whether you want to store the string representations or the bit patterns in the buffer.

Storing the string representations has the problem that the length of the string representation varies with the value. So you need to include separators between the representations of the numbers, or ensure that all representations have the same length by padding (with spaces or leading zeros) if necessary. That would for example work like

#include <stdint.h>
#include <inttypes.h>

#define MAKESTR(x) # x
#define STR(x) MAKESTR(x)

/* A uint32_t can use 10 decimal digits, so let each field be 10 chars wide */
#define FIELD_WIDTH 10

uint32_t gid = 1100;
uint32_t uid = 1000;

size_t buf_size = 0, offset = 0;
char *buffer = NULL, *temp = NULL;
buffer = realloc(buffer, FIELD_WIDTH + 1); /* one for the '\0' */
if (buffer == NULL) {
    exit(EXIT_FAILURE);
}
buf_size = FIELD_WIDTH + 1;
sprintf(buffer, "%0" STR(FIELD_WIDTH) PRIu32, gid);
offset += FIELD_WIDTH;

temp = realloc(buffer, buf_size + FIELD_WIDTH);
if (temp == NULL) {
    free(buffer);
    exit(EXIT_FAILURE);
}
buffer = temp;
temp = NULL;
buf_size += FIELD_WIDTH;
sprintf(buffer + offset, "%0" STR(FIELD_WIDTH) PRIu32, uid);
offset += FIELD_WIDTH;
/* more */

uint32_t valorGID;
uint32_t valorUID;

/* rewind for scanning */
offset = 0;

sscanf(buffer + offset, "%" STR(FIELD_WIDTH) SCNu32, &valorGID);
offset += FIELD_WIDTH;
sscanf(buffer + offset, "%" STR(FIELD_WIDTH) SCNu32, &valorUID);

printf("ValorGID %u ValorUID %u \n", valorGID, valorUID);

with zero-padded fixed-width fields. If you'd rather use separators than a fixed width, the calculation of the required length and the offsets becomes more complicated, but unless the numbers are large, it would use less space.

If you'd rather store the bit-patterns, which would be the most compact way of storing, you'd use something like

size_t buf_size = 0, offset = 0;
unsigned char *buffer = NULL, temp = NULL;
buffer = realloc(buffer, sizeof(uint32_t));
if (buffer == NULL) {
    exit(EXIT_FAILURE);
}
buf_size = sizeof(uint32_t);
for(size_t b = 0; b < sizeof(uint32_t); ++b) {
    buffer[offset + b] = (gid >> b*8) & 0xFF;
}
offset += sizeof(uint32_t);

temp = realloc(buffer, buf_size + sizeof(uint32_t));
if (temp == NULL) {
    free(buffer);
    exit(EXIT_FAILURE);
}
buffer = temp;
temp = NULL;
buf_size += sizeof(uint32_t);
for(size_t b = 0; b < sizeof(uint32_t); ++b) {
    buffer[offset + b] = (uid >> b*8) & 0xFF;
}
offset += sizeof(uint32_t);

/* And for reading the values */
uint32_t valorGID, valorUID;

/* rewind */
offset = 0;
valorGID = 0;
for(size_t b = 0; b < sizeof(uint32_t); ++b) {
    valorGID |= buffer[offset + b] << b*8;
}
offset += sizeof(uint32_t);
valorUID = 0;
for(size_t b = 0; b < sizeof(uint32_t); ++b) {
    valorUID |= buffer[offset + b] << b*8;
}
offset += sizeof(uint32_t);

¹ If you know how malloc etc. work in your implementation, it may be possible to find the size from malloc's bookkeeping data.

OTHER TIPS

The format specifier '%d' is for int and thus is wrong for uint32_t. First uint32_t is an unsigned type, so you should at least use '%u', but then it might also have a different width than int or unsigned. There are macros foreseen in the standard: PRIu32 for printf and SCNu32 for scanf. As an example:

sprintf(buffer, "%" PRIu32, gid);

The representation returned by sprintf is a char*. If you are trying to store an array of integers as their string representatins then your fundamental data type is a char**. This is a ragged matrix of char if we are storing only the string data itself, but since the longest string a uint32_t can yield is 10 chars, plus one for the terminating null, it makes sense to preallocate this many bytes to hold each string.

So to store n uint32_t's from array a in array s as strings:

const size_t kMaxIntLen=11;

uint32_t *a,b;
// fill a somehow
...

size_t n,i;
char **s.*d;

if((d=(char*)malloc(n*kMaxIntLen))==NULL)
   // error!
if((s=(char**)malloc(n*sizeof(char*)))==NULL)
   // error!
for(i=0;i<n;i++)
    {
    s[i]=d+i; // this is incremented by sizeof(char*) each iteration
    snprintf(s[i],kMaxIntLen,"%u",a[i]); // snprintf to be safe
    }

Now the ith number is at s[i] so to print it is just printf("%s",s[i]);, and to retrieve it as an integer into b is sscanf(s[i],"%u",&b);.

Subsequent memory management is a bit trickier. Rather than constantly using using realloc() to grow the buffer, it is better to preallocate a chunk of memory and only alter it when exhausted. If realloc() fails it returns NULL, so store a pointer to your main buffer before calling it and that way you won't lose a reference to your data. Reallocate the d buffer first - again allocate enough room for several more strings - then if it succeeds see if d has changed. If so, destroy (free()) the s buffer, malloc() it again and rebuild the indices (you have to do this since if d has changed all your indices are stale). If not, realloc() s and fix up the new indices. I would suggest wrapping this whole thing in a structure and having a set of routines to operate on it, e.g.:

typedef struct StringArray
{
char **strArray;
char *data;
size_t nStrings;
} StringArray;

This is a lot of work. Do you have to use C? This is vastly easier as a C++ STL vector<string> or list<string> with the istringstream classes and the push_back() container method.

uint32_t gid = 1100;
uint32_t uid = 1000;
char* buffer = NULL;
uint32_t offset = 0;

buffer = realloc(buffer, sizeof(uint32_t));
sprintf(buffer, "%d", gid);
offset += sizeof(uint32_t);

buffer = realloc(buffer, sizeof(uint32_t) + sizeof(buffer));
sprintf(buffer+sizeof(uint32_t), "%d", uid);

uint32_t valorGID;
uint32_t valorUID;

sscanf(buffer, "%4d", &valorGID);
buffer += sizeof(uint32_t);
sscanf(buffer, "%d", &valorUID);

printf("ValorGID %d ValorUID %d \n", valorGID, valorUID);

`

I think this may resolve the issue !

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top