0

So I have a Node struct

struct Node
{
    int x = 0;
};

I make 20 Node*s. My understanding is that Node** is a pointer to the start of an array that holds pointers to Nodes.

constexpr int mazeSize = 20;
Node** testMaze = new Node * [mazeSize];

After this, I started getting warnings and errors when I tried to do anything with it. Examples:

testMaze[0]->position.x == 0; //->Using uninitialized memory `*testMaze` and 

What I understood from this error: *testMaze is dereferencing the array of pointers, which means it's referring to the first Node object in that array. If this is the case than how would I initialize it? If I simply created the Node* as so:

Node* node = new Node;
node->x = 0; 

Than it works fine and there is no need to initialize it, so why not with the way I am doing it? I also don't understand how to initialize a struct.

Another examlpe:

testMaze[0]->x == testMaze[1]->x //->Runtime error: Access violation reading error
testMaze[0]->x = 0; //->Runtime error: Access violation writing error

How can I fix these problems? Thanks.

6
  • 2
    new Node * [mazeSize]; provides an array of pointers. You haven't shown any code that points those pointers anywhere valid, and the warning message suggests that you didn't write such code. Pointer's gonna point, so the solution is create a few Nodes and start pointing. Commented Apr 30, 2021 at 20:48
  • 3
    A Note* is not a Node. You have pointers but no Node to point to. You probably meant to use new Node[mazesize] to actually create Node objects but you should just use std::vector<Node> instead. Commented Apr 30, 2021 at 20:51
  • @user4581301 Ah I think I understand now. I thought new Node * [mazeSize]; would give me pointers with nodes created and pointed to by those pointers, but it actually only makes the pointers and I have to create the nodes myself and get those pointer to point to those nodes. Is that it? Commented Apr 30, 2021 at 20:55
  • 1
    Groovy. Glad to help. Now see if you can use what @FrançoisAndrieux talks about and kill all of the (visible) pointers dead. Pointers almost always slow down code because the computer can't just get the next bit of data, it has to use the pointer to track it down and then load it. But if all of the data is on one straight line, the computer just starts loading stuff in the background and the second item is probably already ready for use by the time you need it if not grabbed in the same read that loaded the first one. Commented Apr 30, 2021 at 21:20
  • 1
    Unrelated ranting: Computers are smart because they can do incredible numbers of stupid things very fast. A lot of the time you get more from letting the computer be stupid and fast than you get from making the computer be smarter. For short runs of data, an O(N) linear search is far faster than any of the fancy O(log(N)) searches because the CPU can just go nope.nope.nope.nope.nope... and not have to make any decisions about the correct path to take or what data to load next. Commented Apr 30, 2021 at 21:27

2 Answers 2

3

Problems:

    constexpr int mazeSize = 20;
    Node** testMaze = new Node *[mazeSize]; //this makes 20 pointers, but the pointers dont point to anything
    testMaze[0]->position.x == 0; //undefined, as testMaze's pointers do not point to anything

This works because you make a new node, not a new pointer to a pointer to a node.

Node * node = new Node;
node->x = 0;

As for this:

    testMaze[0]->x == testMaze[1]->x; //This is an undefined pointer, it doesn't point to anything, and you are trying to access it, so UNDEFINED behavior
    testMaze[0]->x = 0; //This is a undefined pointer, it doesn't point to anything, and you are trying to access it, so UNDEFINED behavior
}

I would just do this:

Node** Make(int size) {
    Node** temp = new Node * [size];
    Node* pool = new Node[size];
    for (int i = 0; i < size; ++i) {
        temp[i] = &pool[i];
    }
}

This makes your array of pointers, makes your pointers point to actual pointers which point to actual values.

Also, don't forget to delete[] your Node**s and Node*s, or you'll have a memory leak!

1
  • Note also that the manual memory management involved can trip you up almost countless ways. Did I allocate? Is there an allocation already there that needs to be dealt with? is it time to delete? Is it safe to delete? Who's job is it to delete, anyway? What sort of copying logic do I need? Can I simply copy the pointer and share the allocation or do I need a new allocation and copy what's at the pointer? You know how the masters deal with the problem? By almost never facing it. They'll do something like this. Commented Apr 30, 2021 at 21:36
2

Always remember that pointer types are separate, concrete types with their own type and size. A pointer to T T* basically boils down to a small chunk of memory that is used to store an address to another memory area, which (hopefully) contains an instance of some type T.

With

Node** testMaze = new Node * [mazeSize];

you are allocating an array of maxSize pointer sized elements of type Node* and sizeof(Node*) (which on modern platforms is usually 4 or 8 bytes, depending if your executable is meant to run in 32 bit or 64 bit mode).

These newly created pointers are not initialized, and thus point to invalid addresses, unless you also zero initialize the array:

Node** testMaze = new Node * [mazeSize] {};
assert (testMaze[0] == nullptr); // holds true

In order to get an maxSize instances of Node, you have to, well, create maxSize instances of Node:

Node** testMaze = new Node* [mazeSize];
for (std::ptrdiff_t i {}; i < maxSize; ++i) {
    testMaze[i] = new Node { /* insert parameters here */ };
}

Given that you are using constexpr, I infer the revision of C++ you are targeting is C++11 or newer. In this case, you should be aware that operator new and operator new[] are almost always the wrong choice when writing modern C++, given that they only returns pointers whose ownership and lifetime you then have to manage by hand.

You most definitely should start using STL containers, such as std::vector and std::array, which are easier to use and can avoid you a great deal of unnecessary pain. If you insist on using new, at least give a look to std::unique_ptr, which wraps a pointer or C array and automatically calls delete or delete[] as soon as it goes out of scope.

7
  • You don't want testMaze[0] = new Node, you want testMaze[i] = new Node. A subtle but important difference. Commented Apr 30, 2021 at 21:28
  • @MarkRansom Dumb me, I made a classic copy-paste mistake. Fixed, thanks!
    – mcilloni
    Commented Apr 30, 2021 at 21:30
  • 2
    Often when you use std::vector you don't need pointers at all, you can make each element of the vector be the full object. Commented Apr 30, 2021 at 21:33
  • @MarkRansom one reason why someone might prefer using a vector of (preferrably smart) pointers instead of just vector<T> is when your T is very large and you need to sort the vector or in general change the order of its elements. Swapping unique_ptrs is instantaneous, while frequently memcpy-ing massively large data structures could turn out being much slower.
    – mcilloni
    Commented Apr 30, 2021 at 21:37
  • 1
    That's where linked lists usually get killed. Sure, you have O(1) insert and delete, but everything else you do, including finding where in the list to insert or delete, is O(N) made painfully slow by by cache misses and pointer chasing. Commented Apr 30, 2021 at 21:47

Not the answer you're looking for? Browse other questions tagged or ask your own question.