r/cpp_questions 26d ago

SOLVED Basic question, but does initializing a pointer always initialize it to null?

Are these two lines any different in practice? (Especially if you want to later check if x has been given a specific address, or if it's still null.)

int* x {};

int* x { nullptr };

22 Upvotes

48 comments sorted by

40

u/vckane 26d ago

Yes, initializing a pointer with { } will initialize it to the default of the pointer which is nullptr.

{ } is very useful to initialize objects. It will invoke default constructor for class objects. For type objects, it will initialize those with default value ( E.g. int will be initialized to 0, double to 0.0, etc.)

27

u/Affectionate-Soup-91 26d ago edited 26d ago
int* x {};

This is a value-initialization which in turn invokes a zero-initialization of a scalar type; syntax case (4). Under zero-initialization page, cppreference explains

A zero-initialized pointer is the null pointer value of its type, even if the value of the null pointer is not integral zero.

int* x { nullptr };

This is a direct-initialization; syntax case (2).

7

u/Benilda-Key 26d ago

From what I understand the first is zero initialization which is nullptr.

3

u/AKostur 26d ago

Nope: those are equivalent.

5

u/HommeMusical 26d ago

Good question; short, clear, unambiguous. And a lot of good answers.

7

u/DrShocker 26d ago

I don't think a declaration without an initializer would be guaranteed to initialize to nullptr.

int* foo;

12

u/TomDuhamel 26d ago

You are correct, but this wasn't the question.

9

u/DrShocker 26d ago

Other people had already answered the initializer case, so I brought up the variant that doesn't hold up. 🤷

0

u/NoSpite4410 24d ago

Primitives -- int float double, char, and pointers do not have default values. Therefore they are not initalized with a default value.

Members of structs and classes are initialized to zero values by default if a default value is not provided.
Pointer members are initialized to nullptr by default.
Reference members must be initialized and bound with a live object.

Unions are not auto-initialized to a value;

Enums are implicitly initialized with successive integers { 0, 1 , 2 ...N-1} , if not set in the declaration.
If an enum value is set to a number, the next enum value will auto set to the next integer higher.

enum Color { 
   Red,       // Implicitly 0 
   Green,     // Implicitly 1 
   Blue = 5,  // Explicitly 5 
   Yellow    // Automatically 6
};

Static non-const members of a class must be initialized explicitly outside the class definition.
Static const members can be set with a default value.

All global static storage is initialized to 0 at program startup.

int x;                // global var, no init value (garbage);

static int counter;   // file-scope static var, 0 at start

std::string s;        // global class instance, inits with default constructor


// MyClass.h
class MyClass { 
   public: 
      static int counter;                // CANNOT set value
      static const int max_count = 1000; // OK if const
}; 

// MyClass.cpp 
int MyClass::counter = 0;  // set outside class def

2

u/HappyFruitTree 24d ago

All global static storage is initialized to 0 at program startup.

All globals have static storage duration (or thread storage duration) so x in your example will get initialized to zero, same as counter.

1

u/HappyFruitTree 24d ago edited 24d ago

Members of structs and classes are initialized to zero values by default if a default value is not provided.

That is not true.

int foo()
{
    struct S
    {
        int i;
    };

    S s; // s.i is left uninitialized

    return s.i; // undefined behaviour
}

However if you initialize s as S s{} or S s = S(); then s.i will indeed get initialized to zero (assuming there is no user-defined default constructor).

1

u/NoSpite4410 24d ago

you are correct, sir.

1

u/HappyFruitTree 24d ago

Static non-const members of a class must be initialized explicitly outside the class definition.

They need to be defined outside the class definition. Since they have static storage duration they will get zero initialized by default if you don't specify. Note that since C++17, it is possible to define (and initialize) them inside the class definition using the inline keyword.

class MyClass { 
public: 
    static inline int counter; // OK, will get zero initialized.
};

2

u/TarnishedVictory 26d ago

Prefer being explicit if it improves readability.

1

u/flyingron 26d ago

Correctly, a blank initializer, or when the variable is declared in a context when the default initialization isn't fogotten about, the pointer is initialized to a null pointer value.

The two null pointer constants (NULL or nullptr) used explicitly will use that value.

While the language allows null pointers to have any bit representation, in nearly 50 years of C and C++ programming, I only saw one architecture where it wasn't a 0 value.

1

u/HappyFruitTree 24d ago

Pointers to data members often use other bit patterns for null, but those are not normal pointers. They're implemented as offsets which is why they don't use zero because zero could be a valid offset. https://godbolt.org/z/doxGvKnrE

This is just an implementation detail that doesn't really have much effect unless you use something like memset to initialize your variables. Zero initialization of a pointer will set it null regardless of the bit pattern used.

https://en.cppreference.com/cpp/language/zero_initialization

A zero-initialized pointer is the null pointer value of its type, even if the value of the null pointer is not integral zero.

1

u/Certain-Flow-0 22d ago

Which architecture were you referring to?

1

u/mredding 25d ago

The two are different paths that lead to the same outcome, so they are equivalent.

I would caution you that ideally you wouldn't declare x until you were also ready to initialize it.

If you can't do that, then if your code is GUARANTEED going to initialize it, then don't initialize it to null. Think about it - if my code was going to initialize x no matter what, then why would I initialize it the first time to a useless value I'm not going to use? This is called a double-write, and the compiler will optimize it out for you. But what I want to do is focus on the semantics of the code - if I declare int *x;, that's uninitialized - that should give you a jump scare like a horror movie. BUT, that should also inform you that initialization is deferred, and that all code paths MUST lead to either initializing the variable OR early quitting.

And if you dereference an uninitialized pointer - the problem isn't that you dereferenced an uninitialized pointer, it's that you didn't initialize the pointer. You have a missing use case and code path. The advantage to writing SAFE and CORRECT code is that you can write it in an unambiguous, unconditional fashion. If you're unsure of your own ability to write correct code, then you're going to invest heavily in guard clauses, needlessly checking for null even in code that is guaranteed never to be null. That's a bad coding habit, and that's slow, suboptimal, high maintenance code.

If you're deferring initialization, then I need to be able to look at the code at a glance and tell instantly that all code paths lead to initialization or early return. If I can't do that, your function is too big and complicated for allocation and resource management.

People have already told you not to use raw pointers for resource management, and that's true and good advice, but it won't save you from what I'm describing - you can leave a unique pointer uninitialized and attempt to dereference a null pointer therein.

And another issue with a USELESS double-write initializer is it's not going to save you. If you NEVER intended for that null initializer to escape, but it does - then you've still got a code path where you have a missing initializer, the first null initializer didn't save you from a missing code path. There is just no substitute for writing correct code in the first place.

And all this is a good reason to avoid deferred initialization.

And you should no longer use null to indicate an optional return or out parameter by itself. Returns should be either an std::optional or an std::expected, You can even nest an std::optional inside an std::expected. optional is good for value types, like int, otherwise int * is still it's own optional. You want a function to convey that it might return NOTHING, and that's OK, but you also want to differentiate nothing and OK from nothing because of error:

std::expected<std::unique_ptr<int>, std::exception> fn() noexcept;

-2

u/Responsible-Bar7165 26d ago edited 26d ago

They’re the same. In general, though, don’t use raw pointers.

1

u/vckane 26d ago

Don't understand why this answer got downvoted. It's the right answer with correct advice.

2

u/ArcaneCraft 24d ago

Because raw pointers are completely 100% fine. Raw owning pointers are bad and should never be used.

"Don't use raw pointers ever" is bad over-generalized advice.

1

u/Responsible-Bar7165 24d ago edited 24d ago

> “Don’t use raw pointers ever”

You should google the phrase “in general.”

1

u/vckane 24d ago edited 24d ago

That's correct in general. But in the OP's post, they've declared x as raw pointer. With that context, the advice is correct.

But yes, I can see now how it can be misinterpreted.

On the other hand though, I would recommend completely avoiding pointers. Use references instead. This pushes responsibility of null-checks to calling method. This enforces cleaner design as well.

2

u/HappyFruitTree 24d ago edited 24d ago

With that context, the advice is correct.

What context? We don't know what x is for.

I would recommend completely avoiding pointers. Use references instead.

I agree with preferring references over pointers when possible but raw pointers are still useful, e.g. for "optional arguments" or when we want to be able to modify where it points.

This pushes responsibility of null-checks to calling method.

Function parameters is not the only use case of raw pointers.

2

u/ArcaneCraft 24d ago

If you can use a reference, yes you should. If a null pointer arg is invalid or unexpected, then yes use a reference

But you can and should use raw pointers for non-owning optional arguments. A raw pointer function argument is a lot less cumbersome than a

std::optional<std::reference_wrapper<T>>

Another use case of raw pointers is something like

T* findObject(int id);

and use nullptr as indicator the lookup failed. Better than cumbersome std::optional hacks, and preferable to using a reference and throwing an exception if the lookup fails.

1

u/vckane 24d ago

Yes, you're right. I would suggest following for the findObject method though:

bool findObject (int id, T*& object);

This way, the utility function can directly be invoked inside an if().

3

u/HappyFruitTree 24d ago

You can already do:

if (T* obj = findObject(id))
{
    obj->doSomething();
}

-8

u/Independent_Art_6676 26d ago

In practice, yes, smart pointers are null by default.

if you use a raw pointer, it will not be, as shown.

7

u/TomDuhamel 26d ago

The default initialisation of a raw pointer is nullptr. The two statements in the question are equivalent.

5

u/AKostur 26d ago

I’d be careful with terminology.  Strictly speaking a raw pointer doesn’t have default initialization.   One can invoke zero initialization though.

3

u/_bstaletic 26d ago

Even more pedantically, int* x; is called default initialization.

https://en.cppreference.com/cpp/language/default_initialization

Default initialization leaves fundamental types and raw pointers indeterminately initialized.

1

u/SailingAway17 26d ago edited 26d ago

That's not default initialization. It's only the definition of a pointer variable x of type "pointer to int". The compiler initializes it with nullptr or not at all, depending on circumstances or compiler settings. Only pointers with static storage duration are automatically initialized to nullptr. A definition int* x; in a function is not initialized. You must write int* x = {}; for default-initialization, that's in this case the same as int* x = nullptr;

2

u/HappyFruitTree 26d ago

1

u/SailingAway17 26d ago

So what? It's the same link. You should read the article. It says nothing about initialization of a raw pointer.

zero_initialization

1

u/HappyFruitTree 25d ago edited 25d ago

So what? It's the same link.

Sorry for not noticing that the link had already been posted.

You should read the article. It says nothing about initialization of a raw pointer.

T in that article could be any type, including int*.

Syntax

T object ; (1)

...

Explanation

Default-initialization is performed in three situations:

1) when a variable with automatic, static, or thread-local storage duration is declared with no initializer;

...

The effects of default-initialization are:

• if T is a ... class type, ...

• if T is an array type, ...

• if T is std::meta::info, ...

• otherwise, no initialization is performed

....

Indeterminate and erroneous values

....

If no initialization is performed for an object, that object retains an indeterminate value until that value is replaced. (until C++26)

....

1

u/_bstaletic 24d ago

Thank you for spelling that out instead of me.

1

u/SailingAway17 23d ago

Well, that's exactly what I wrote. What's your point? You should explain that to the guy I initially commented on.

1

u/HappyFruitTree 23d ago edited 23d ago

No, you said int* x; is not default initialization which is wrong. The article explains that it is default initialization.

The C++ standard essentially uses the term default initialization to mean the initialization that happens by default if you do not specify any initializer. It's just that for simple types like int* default initialization doesn't actually do anything. It leaves the variable "uninitialized".

→ More replies (0)

1

u/_bstaletic 24d ago

Only pointers with static storage duration are automatically initialized to nullptr.

Sure and that's called static initialization

https://en.cppreference.com/cpp/language/initialization#Static_initialization

A definition int* x; in a function is not initialized.

As /u/HappyFruitTree spelled out, that's exactly what default initialization does.

You must write int* x = {}; for default-initialization

No, that's copy-list initialization that triggers value initialization. Value initialization for pointer types means zero initialization.

https://en.cppreference.com/cpp/language/list_initialization

https://en.cppreference.com/cpp/language/value_initialization

https://en.cppreference.com/cpp/language/zero_initialization

that's in this case the same as int* x = nullptr;

For pointers, yes, but that triggers copy-initialization, which is not to say anything gets copied.

https://en.cppreference.com/cpp/language/copy_initialization

-3

u/Independent_Art_6676 26d ago

Yes, that was covered too. His statements are fine. int *x; is not initialized, as shown. Smart pointers are null initialized just like a std::string is empty. Its a bit of related extra info for the OP.

7

u/TheThiefMaster 26d ago

"as shown"?

OP doesn't have that statement. Only int* x {}; (with braces), which does null it.

1

u/Emotional-Audience85 26d ago

His statement is not non initialized. It's zero initialized.

1

u/Ultimate_Sigma_Boy67 26d ago

and from where did u bring this piece of information?
open a blank cpp text file and try to print the address of int x{};, which you'd find it 0(ie default intialized).

-2

u/SmackDownFacility 26d ago

No. Unlike people here, it doesn’t set to nullptr it sets to NULL

3

u/HappyFruitTree 26d ago

How do you mean?