2

I have such two struct

struct table_element
{
    struct table_val * table_val_arr;
    int count_arr;
};

struct hash_table
{
    struct table_element table_element_arr[MAX_NUMBER];
};

and here my test method

void test(struct hash_table * table)
{
    int count;
    struct table_element * tab_element;

    for(count = 0; count < MAX_NUMBER; count++)
    {
        tab_element = &table->table_element_arr[count]; 

        if(tab_element->table_val_arr == NULL)
        {
            printf("\nNULLLL!!!!!\n");
        }
        else
        {
            printf("\nOK!!!!!\n");
        }
    }
}

and here how I use it

int main(int argc, char **argv)
{
    struct hash_table m_hash_table;

    test(&m_hash_table);
...

I expect that all value would be NULL, but sometimes I get OK sometimes NULL...

What am I doing wrong?

How to init it with NULL?

Sirop4ik
  • 4,543
  • 2
  • 54
  • 121

5 Answers5

4

Non-static variables defined inside of a function have indeterminate values if not explicitly initialized, meaning you can't rely on anything they may contain.

You can fix this by giving an initializer for the variable:

struct hash_table m_hash_table = {{NULL, 0},{NULL, 0},/*repeat MAX_NUMBER times*/};

Or by using memset:

memset(&m_hash_table, 0, sizeof(m_hash_table));
dbush
  • 205,898
  • 23
  • 218
  • 273
3

If you don't explicitly initialise a variable in C, it'll have an undefined value. eg.

int fish;  // could be zero, -100, 3805, ...anything
int chips = 5;  // will definitely be 5.

The same is true of pointers. They could point anywhere. And finally, the same is true of a structure's members.

There are two common approaches to this 'problem' depending on your needs.

  1. memset the whole thing to zero:
struct hash_table m_hash_table;
memset( &m_hash_table, 0, sizeof(m_hash_table) );

Result: all the variables will be zero, all the pointers will be NULL1.

  1. Explicitly set everything by hand:
struct hash_table m_hash_table;
for (int i = 0; i < MAX_NUMBER; i++)
{
    m_hash_table.table_element_arr[i].table_val_arr = NULL;
    m_hash_table.table_element_arr[i].count_arr = 0;
}

A third option is to provide initialisation when you declare the struct, but it's logically equivalent to option 2.

struct hash_table m_hash_table = { { NULL, 0 }, { NULL, 0 }, ... /*etc*/ };

1 As per the comments, it is true that there exist some architectures where a bit pattern of all zeros is not equivalent to NULL, and hence the memset( ..., 0, ...) approach is not strictly valid. However, for all practical purposes, on any modern platform, it's a perfectly valid, idiomatic solution.

(IMHO anyone using an architecture where this isn't true isn't going to be looking for advice on SO about how to initialise their structures!)

Edd Inglis
  • 1,067
  • 10
  • 22
  • 2
    `memset` is not guaranteed to set the pointers to `0`. Option 2 is best. – S.S. Anne Jan 24 '20 at 17:30
  • If you use `C99`, you'll get more initialisation options. – Neil Jan 24 '20 at 17:39
  • @S.S.Anne Can you elaborate on that? Do you mean not all implementations equate 0 with `NULL`? The top answer here says the C standard defines `NULL = (void*)0` : https://stackoverflow.com/questions/1296843/what-is-the-difference-between-null-0-and-0 . But then later on it does say `NULL` is implementation-defined. – yano Jan 24 '20 at 23:14
  • 1
    @Neil Thanks, but that seems to suggest even more strongly than the SO question I linked that 0 equates to `NULL`. – yano Jan 24 '20 at 23:36
  • 2
    Ok, yeah I guess the answer is all zero bits isn't necessarily the same as `NULL` for all implementations. I assume that on such implementations, doing `myPtr = 0;` will substitute the correct value: https://stackoverflow.com/questions/398883/how-to-set-pointer-to-a-memory-to-null-using-memset , although that is tagged c++. – yano Jan 24 '20 at 23:39
  • This has caused so much confusion that, I guess, the designers of Java decided to change `0` to `null`. – Neil Jan 24 '20 at 23:56
  • 3
    0 is `NULL`. Bit-wise 0 may not be `NULL`. – S.S. Anne Jan 25 '20 at 02:23
3

You declared m_hash_table as an automatic variable. Such variables are usually located on the stack. The stack space may be filled with random content.

You have three options.

  1. Declare it as a static variable: static struct hash_table m_hash_table;
  2. Use memset(): memset(&m_hash_table, 0, sizeof(m_hash_table));
  3. Use explicit initializer: struct hash_table m_hash_table = {};

UPDATE#1

According to this http://c-faq.com/null/machexamp.html information options #1 and #2 do not work correctly on some hardware. The option #3 gives the desired result.

UPDATE#2

The discussion below reveals a new truth. Option #1 is the best.

Sergey
  • 522
  • 2
  • 8
  • @Neil, please explain under what conditions memset is doing wrong. In this case (and many others) everything works correctly. – Sergey Jan 24 '20 at 17:57
  • On your specific hardware, the value of null is zero, (as it commonly is.) The [C FAQ](http://c-faq.com/null/) has an entire section on null pointers. Specifically, null is not guaranteed to be all-bits-zero. – Neil Jan 24 '20 at 18:02
  • 1
    @Neil, you're right. But I still do not believe this. – Sergey Jan 24 '20 at 18:24
  • You are right, it's very common to have all-bits-zero as a representation of a null pointer, but this is [not always the case](http://c-faq.com/null/machexamp.html); it's more that it will be confusing for others who read the code to see what one is doing. – Neil Jan 24 '20 at 18:43
  • I agree that Option 3 is the best; with the _caveat_ that it's `C99`; `C90` would not allow this. #1 _will_ work, just #2. See https://stackoverflow.com/questions/13251083/the-initialization-of-static-variables-in-c. – Neil Jan 24 '20 at 18:51
  • @Neil, as far as I understand, #1 is equivalent to #2, because the structure is mapped to the memory initialized with zeros. – Sergey Jan 24 '20 at 19:34
  • 1
    On trying it, I think `m_hash_table = {};` is a GNU extension / C++, not `C99` as I said; one could use `m_hash_table = {{{0,0}}};`. @Sergey `memset` is not the same as all the others, having [specific rules](https://stackoverflow.com/a/13251173/2472827) for initialisation _vs_ just setting all the same bits with no distinction for the underlaying types, (but often it is equivalent on most hardware, but be aware of `enums` and pointers.) – Neil Jan 24 '20 at 20:20
  • Cool! All the references in place! This means that the option #1 is the best. Hope this discussion will help those who will read it. Thanks for @Neil – Sergey Jan 24 '20 at 20:40
  • Note that #1 changes the storage class of the object, which typically would not be desirable unless one intended them to be `static` anyway; depends on the design of one's programme. – Neil Jan 24 '20 at 21:56
3

The struct hash_table m_hash_table; is automatic storage, (vs say, static, in which case it would be automatically initialised.) This means the contents of the variable are indeterminate. One could initialise it several ways, see initialisation, (or the other answers.) However, I think that this is important to know that memset is not a proper way to initialise a null pointer, (the C FAQ has an entire section on null pointers.) Like Pascal's nil or Java's null, 0 in pointer context has a special meaning in C, the null pointer. It commonly is all-bits-zero, leading to the mistaken impression that 0 is actually all-bits-zero, but this is not always the case. The general idiomatic way is to have a constructor in which you set any null pointers with explicit,

te->table_val_arr = 0; /* or NULL. */
te->count_arr = 0;

Edit: three initialisations are shown:

#include <stddef.h>
#include <assert.h>

/* `struct table_val` is undefined in this limited context. */
struct table_element {
    int * table_val_arr;
    int count_arr;
};

/** `te` is a value that gets initialised to be empty. */
static void table_element(struct table_element *const te) {
    assert(te);
    te->table_val_arr = 0; /* Or `NULL`, depending on your style. */
    te->count_arr = 0;
}

struct hash_table {
    struct table_element table_element_arr[100];
};

static size_t hash_table_size =
    sizeof ((struct hash_table *)0)->table_element_arr
    / sizeof *((struct hash_table *)0)->table_element_arr;

/** `ht` is a value that gets initialised to be empty. */
static void hash_table(struct hash_table *const ht) {
    size_t i;
    assert(ht);
    for(i = 0; i < hash_table_size; i++)
        table_element(ht->table_element_arr + i);
}

/* This is automatically initialised to all-elements-zero, (which is not
 necessary all-bits-zero.) */
static struct hash_table g_hash_table;

int main(void) {
    struct hash_table m_hash_table = {{{0,0}}}; /* Initialiser. */
    struct hash_table table; /* Garbage. */
    hash_table(&table); /* Now fixed. */
    return 0;
}

The dynamic way of using constructor functions is scalable to large objects and objects that one doesn't want to necessarily initialise with zero; C++ expands this greatly to RAII. The initialisation in the declaration is limited to constant expressions, and thus is probably the most efficient. The static option changes the storage class of the object and is probably unsuitable except for objects that one wanted to declare static anyway.

Neil
  • 1,767
  • 2
  • 16
  • 22
  • seems rare, but still valid. I very much prefer using `NULL` rather than 0 for pointers, that way I know at a glance I'm dealing with pointers. But if I did `myPtr = 0;` in one of these "different" implementations, the compiler would know to substitute whatever value equates to a `NULL` pointer in place of 0? Same for all the checks `if(myPtr) { ... }`, etc? – yano Jan 24 '20 at 23:49
  • 1
    `if(myPtr)` is equivalent to `if(myPtr != 0)` which is, assuming `NULL` is a pre-processor macro which evaluates to 0, the same as `if(myPtr != NULL)`. `0` in this sense, is a null-pointer, and not necessarily all-bits-zero. – Neil Jan 25 '20 at 00:33
  • 1
    The compiler can tell the difference between 0 in pointer contexts and 0 in numerical contexts in _almost_ all cases, see http://c-faq.com/null/confused2.html. (Using `NULL` is a great way to show the intent of the code explicitly, but doesn't necessarily save you from those cases.) – Neil Jan 25 '20 at 01:01
1

A colleague (not on SO) has suggested this answer: Partially initializing a C struct

Which says (in essence) if you initialise the first element of your structure, the compiler will automatically initialise everything else to zero or NULL (as appropriate) for you.

Copying from that...

10 If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate. If an object that has static storage duration is not initialized explicitly, then:

—if it has pointer type, it is initialized to a null pointer;

—if it has arithmetic type, it is initialized to (positive or unsigned) zero;

—if it is an aggregate, every member is initialized (recursively) according to these rules;

—if it is a union, the first named member is initialized (recursively) according to these rules.

...

21 If there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate, or fewer characters in a string literal used to initialize an array of known size than there are elements in the array, the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration.

Community
  • 1
  • 1
Edd Inglis
  • 1,067
  • 10
  • 22
  • Good answer. To expand, _p21_ is new in C99 and does not appear in the `-ansi` C89/90 version, (presumably?) With improving C99 support, (especially MSVC,) I expect this will probably matter less now. https://stackoverflow.com/a/38788270/2472827 – Neil Jan 29 '20 at 22:41