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?

2
  • Where is the definition of struct table_val? Commented Jan 24, 2020 at 17:17
  • 1
    @GovindParmar, a reasonable question, but I'm not sure it matters much here. Commented Jan 24, 2020 at 17:26

5 Answers 5

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));
Sign up to request clarification or add additional context in comments.

Comments

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!)

7 Comments

memset is not guaranteed to set the pointers to 0. Option 2 is best.
If you use C99, you'll get more initialisation options.
@Neil Thanks, but that seems to suggest even more strongly than the SO question I linked that 0 equates to NULL.
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: stackoverflow.com/questions/398883/… , although that is tagged c++.
0 is NULL. Bit-wise 0 may not be NULL.
|
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.

9 Comments

@Neil, please explain under what conditions memset is doing wrong. In this case (and many others) everything works correctly.
On your specific hardware, the value of null is zero, (as it commonly is.) The C FAQ has an entire section on null pointers. Specifically, null is not guaranteed to be all-bits-zero.
@Neil, you're right. But I still do not believe this.
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; it's more that it will be confusing for others who read the code to see what one is doing.
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 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.)
|
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.

3 Comments

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?
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.
The compiler can tell the difference between 0 in pointer contexts and 0 in numerical contexts in almost all cases, see 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.)
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.

1 Comment

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. stackoverflow.com/a/38788270/2472827

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.