is this safe to clear a structure?

Thread Starter

bug13

Joined Feb 13, 2012
2,002
Hi guys

I often need to clear a structure, what I am doing now is just write a function to clear one element by one element. Just wondering is it safe to clear a structure as follow code. I am using XC8 compiler.

Thanks guys!

Code:
// my attempt to make a struct to cover everything
typedef struct{
  uint8_t blah_1;
  uint16_t blah_2;
  uint32_t blah_3;
  foo_struct_t foo_1;
  foo_union_t foo_2;
  uint8_t bit_1   : 1;
   ...
  uint8_t bit_8   : 1;
}blah_t;

void clear_struct(blah_t *blah){
  uint8_t len;
  len = sizeof(*blah);
  for(uint8_t i = 0; i < len; i++){
    blah[i] = 0;
  }
}
 

JohnInTX

Joined Jun 26, 2012
4,787
For stuff like this to work you have to depend on the compiler to allocate the memory for each instance of the struct contiguously. When that is guaranteed, you can also use 'memset' to load the memory. I've used a few constructs like these in some small XC8 stuff. But since PICs have RAM in banks, you would have to scour the documentation to ensure that it always stores a struct addressed contiguously. In my experiences with XC8 and HiTech PICC-18 this has been the case. But unless it is specifically spelled out in the documentation, I'd be more comfortable with assigning each variable to 0. That will also improve portability across compilers and possibly PIC families.

Good luck!
 

Thread Starter

bug13

Joined Feb 13, 2012
2,002
For stuff like this to work you have to depend on the compiler to allocate the memory for each instance of the struct contiguously. When that is guaranteed, you can also use 'memset' to load the memory. I've used a few constructs like these in some small XC8 stuff. But since PICs have RAM in banks, you would have to scour the documentation to ensure that it always stores a struct addressed contiguously. In my experiences with XC8 and HiTech PICC-18 this has been the case. But unless it is specifically spelled out in the documentation, I'd be more comfortable with assigning each variable to 0. That will also improve portability across compilers and possibly PIC families.

Good luck!
Good point, I am glad I asked. Thanks @JohnInTX
 

nsaspook

Joined Aug 27, 2009
13,272
Initializing each structure member to zero will make every member to zero, but you'll miss the padding bytes if there are specific structure alignment pragmas that might be used if you transfer binary data from 8-bit to 32-bit machines for example. The C standard guarantees value zero initialization to bit zeros for integral types, not for floating-point values or pointers.
 
Last edited:

JohnInTX

Joined Jun 26, 2012
4,787
I got curious and here's some info from the XC8 User's Guide. It talks about the differences in allocation between PIC families and the challenges of dealing with a banked architecture. I think the more general method of assigning values to each field would be a good idea. Others here may have more insight.
5.5.2.1.2 Non-Auto Variable Size Limits
Arrays of any type (including arrays of aggregate types) are fully supported by the compiler.
So too are the structure and union aggregate types, see 5.4.4 Structures and
Unions. These objects can often become large in size and can affect memory
allocation.
When compiling for enhanced mid-range PIC devices, the size of an object (array or
aggregate object) is typically limited only by the total available data memory. Single
objects that will not fit into any of the available general purpose RAM ranges will be
allocated memory in several RAM banks and accessed using the device’s linear GPR
(general purpose RAM).
Note that the special function registers (which reside in the data memory space) or
memory reservations in general purpose RAM can prevent objects from being allocated
contiguous memory in the one bank. In this case objects that are smaller than
the size of a RAM bank can also be allocated across multi-banks. The generated code
to access multi-bank objects will always be slower and the associated code size will be
larger than for objects fully contained within a single RAM bank.
When compiling for PIC18 devices, the size of an object is also typically limited only by
the data memory available. Objects can span several data banks.
On baseline and other mid-range devices, arrays and structures are limited to the maximum
size of the available GPR memory in each RAM bank, not the total amount of
memory remaining. An error will result if an array is defined which is larger than this
size.
 

WBahn

Joined Mar 31, 2012
30,058
Hi guys

I often need to clear a structure, what I am doing now is just write a function to clear one element by one element. Just wondering is it safe to clear a structure as follow code. I am using XC8 compiler.

Thanks guys!

Code:
// my attempt to make a struct to cover everything
typedef struct{
  uint8_t blah_1;
  uint16_t blah_2;
  uint32_t blah_3;
  foo_struct_t foo_1;
  foo_union_t foo_2;
  uint8_t bit_1   : 1;
   ...
  uint8_t bit_8   : 1;
}blah_t;

void clear_struct(blah_t *blah){
  uint8_t len;
  len = sizeof(*blah);
  for(uint8_t i = 0; i < len; i++){
    blah[i] = 0;
  }
}

ABSOLUTELY NOT!!!!!!

In your clear_struct() function, blah is a pointer to an object of type blah_t.

But a pointer to a variable of type FRED is indistinguishable from a pointer to an array of variables of type FRED.

What would you expect to happen if you had

*blah = 0;

This makes no sense. *blah is a structure of type blah_t and setting a structure equal to zero is undefined.

But this is exactly the same as

blah[0] = 0

which is what will happen during the first pass through your loop.

blah is NOT the ith element in the structure blah (there's no way to access structure elements using array subscript notation for the simple reason that structure elements do not have to be all the same size, and so the compiler doesn't have the information it needs to calculate the offset). It is the entire ith blah structure and the array of blah structures pointed to by blah (which the interpretted as the address at which the first blah_t structure in the array is stored).

So blah is the blah_t structure located in memory just after the one that blah points to -- in other words, beyond the memory allocated to the data associated with the pointer you passed -- you've just strayed into stomping on memory belonging to other things, which is seldom a recipe for happiness.

If, instead, you use memset() to set a block of memory consisting of sizeof(*blah) starting at blah to zero, then that should zero out the entire contents of the structure. HOWEVER, that does not mean that each of the elements of the structure is "zero". This is particularly true for elements that are pointers because while a NULL pointer must BEHAVE as though it is zero, the actual value of a NULL pointer can be anything the compiler chooses to use and a literal zero stored as the value of a pointer may not behave as a NULL pointer because that isn't the value that the generated code is looking for. Also, if you aren't using IEEE-754 floating point values, a pattern of all zeroes may or may not be interpreted at 0.0.
 
Last edited:

WBahn

Joined Mar 31, 2012
30,058
For stuff like this to work you have to depend on the compiler to allocate the memory for each instance of the struct contiguously. When that is guaranteed, you can also use 'memset' to load the memory. I've used a few constructs like these in some small XC8 stuff. But since PICs have RAM in banks, you would have to scour the documentation to ensure that it always stores a struct addressed contiguously. In my experiences with XC8 and HiTech PICC-18 this has been the case. But unless it is specifically spelled out in the documentation, I'd be more comfortable with assigning each variable to 0. That will also improve portability across compilers and possibly PIC families.

Good luck!
I'm going out on a limb here and speculating, so I could be completely off base.

Just as the language requires that a NULL pointer behave as if it were actually the value zero regardless of the value chosen to represent it, I would imagine that the compiler would be responsible for generating the code necessary to make a structure behave as though it were allocated in a contiguous block of memory. That would mean the compiler has the authority to split a structure across banks, but it is responsible for making the program not see that that's the case.

Which is not to say that you couldn't still get in trouble. Just as you can get at the actual value stored for a NULL pointer simply by accessing the memory as something else (such as an unsigned int or whatever size is appropriate) via casting, you could probably get at the underlying bank structure.

Whether this would/could cause problem would be at what level the pretense is made. If code is generated that makes the stack and heap behave as a flat memory space, then there should be no problem. If the code is generated just to do this at the structure level, then memset() would likely cause problems if the structure was split.
 

xox

Joined Sep 8, 2017
838
For the general case a simple set of macros will get the job done.

Code:
#include <string.h>
#define ZERO_ARRAY(array, length) memset(array, 0, length * sizeof((array)[0]))
#define ZERO_OBJECT(data) ZERO_ARRAY(&data, 1)
Example:

Code:
struct foo
{
    int a, b, c;
    double d;
};

struct bar
{
    int e;
    float f;
};

#include <stdio.h>

void dump(char* tag, void* data, size_t size)
{
    printf("%s: ", tag);
    unsigned char* bytes = data;
    for(size_t index = 0; index < size; ++index)
    {
        unsigned byte = bytes[index];
        printf("%s", byte < 0x10 ? "0" : "");
        printf("%x", byte);
    }
    printf("\n\n");
}

int main(void)
{
    struct foo object;
    dump("foo [before]", &object, sizeof(object));
    ZERO_OBJECT(object);
    dump("foo [after] ", &object, sizeof(object));

    const size_t size = 10;
    struct bar array[size];
    dump("bar [before]", array, sizeof(array));
    ZERO_ARRAY(array, size);
    dump("bar [after] ", array, sizeof(array));

    return 0;
}
Of course that doesn't address the concerns raised by others here but from a practical standpoint at least those aren't really much to worry about given their unlikelyhood. I mean sure you could resort to writing lots of redundant code just to be certain that you aren't dabbling in undefined behaviour but the truth of the matter is that compiler vendors typically work very hard to adhere to the principle of least astonishment. The most productive approach is to simply operate on the assumption that these so-called corner cases don't even exist. When and if things ever do break, fine, just fix it and move on.
 

Thread Starter

bug13

Joined Feb 13, 2012
2,002
ABSOLUTELY NOT!!!!!!

In your clear_struct() function, blah is a pointer to an object of type blah_t.

But a pointer to a variable of type FRED is indistinguishable from a pointer to an array of variables of type FRED.

What would you expect to happen if you had

*blah = 0;

This makes no sense. *blah is a structure of type blah_t and setting a structure equal to zero is undefined.

But this is exactly the same as

blah[0] = 0

which is what will happen during the first pass through your loop.

blah is NOT the ith element in the structure blah (there's no way to access structure elements using array subscript notation for the simple reason that structure elements do not have to be all the same size, and so the compiler doesn't have the information it needs to calculate the offset). It is the entire ith blah structure and the array of blah structures pointed to by blah (which the interpretted as the address at which the first blah_t structure in the array is stored).

So blah is the blah_t structure located in memory just after the one that blah points to -- in other words, beyond the memory allocated to the data associated with the pointer you passed -- you've just strayed into stomping on memory belonging to other things, which is seldom a recipe for happiness.

If, instead, you use memset() to set a block of memory consisting of sizeof(*blah) starting at blah to zero, then that should zero out the entire contents of the structure. HOWEVER, that does not mean that each of the elements of the structure is "zero". This is particularly true for elements that are pointers because while a NULL pointer must BEHAVE as though it is zero, the actual value of a NULL pointer can be anything the compiler chooses to use and a literal zero stored as the value of a pointer may not behave as a NULL pointer because that isn't the value that the generated code is looking for. Also, if you aren't using IEEE-754 floating point values, a pattern of all zeroes may or may not be interpreted at 0.0.
hmmmm... I absolute did not see this. I guess I can't be lazy and clear the element one by one then. Thanks for your inputs
 

Thread Starter

bug13

Joined Feb 13, 2012
2,002
How about this, will this work?? I won't be using float

Code:
typedef union{
  uint8_t data[size to cover the structure];
  struct{
    uint8_t blah1;
    uint16_t blah2;
    uint32_t blah3;
    uint8_t bit1;
    ...
    uint8_t bit8;
  }
}foo_t
Then just loop through the array one by one.
 

WBahn

Joined Mar 31, 2012
30,058
How about this, will this work?? I won't be using float

Code:
typedef union{
  uint8_t data[size to cover the structure];
  struct{
    uint8_t blah1;
    uint16_t blah2;
    uint32_t blah3;
    uint8_t bit1;
    ...
    uint8_t bit8;
  }
}foo_t
Then just loop through the array one by one.
If there are issues with splitting across banks, I would imagine that the same issues might exist here.

Also, how do you know how big to make your array? It might be 13 bytes today and 15 bytes tomorrow, depending on how the compiler chooses to do things.
 
Top