-2

I am currently working on a legacy code (still proprietary, so we'll have to make do with an MRE instead; and by legacy I mean code written in 1991). This is what I've come across:

#include <iostream>

void foo(void*& ptr2) {
    
    // whatever
    
}

int main() {
    int** ptr = new int*[5];
    void* ptr2;
    int i = 1;
    for(int j = 0; j < 5; j++) {
        ptr[j] = new int;
        ptr[j][0] = i++;
    }
    ptr2 = ptr;
    foo(ptr2);

    // further operations here... and a comment which suggests the above was done to prevent accidental double dereferencing when ptr2 was passed by reference to another function
    return 0;
}

How does the logical layout of ptr2 look like in this case? I am assuming that ptr[0][0] becomes ptr2[0], ptr[1][0] becomes ptr2[1] and so on, but the print statements (not required in the MRE, just standard cout statements on various indices) prove my assumption wrong. What am I missing here?

justanotherguy
  • 399
  • 5
  • 16
  • 3
    This code shouldn't work. `ptr` isn't like an array of arrays, it's a *jagged array*, where each elements of `ptr` is a pointer to another array (of a single element, but still). And on a 64-bit system the size of pointers are unlikely to be equal to the size of an `int`. Any attempt to index `ptr2` could lead to *undefined behavior*. – Some programmer dude Nov 24 '22 at 07:27
  • 2
    Now this is different. To use the pointer in any meaningful way in the `foo` function is *must* be casted back to the real type (i.e. `int** real_pointer = (int**) ptr2;`). Then dereferencing the pointers would be fine. – Some programmer dude Nov 24 '22 at 07:46
  • `void*& ptr2` is almost certainly wrong. Sure that it isn't `void* ptr2`? – user17732522 Nov 24 '22 at 07:49
  • 1
    @user17732522 Since dynamic memory is expected, foo can change the pointer to point to other dynamic memory (and hopefully release the passed pointer). Not the code I like to work with in C++, but not wrong. – stefaanv Nov 24 '22 at 07:53
  • Also, why not just use a `const **` instead of doing all this? Would that have been wrong? – justanotherguy Nov 24 '22 at 07:55
  • Please do not edit your question such that its meaning radically changes. `void* ptr2;` is not `int* ptr2`. I am reverting the changes. If you have a separate question about `void*`, ask a separate question. – n. m. could be an AI Nov 24 '22 at 08:07
  • @n.m., the original question was wrong and meaningless and corrected just in time. Please do not revert the edit. – justanotherguy Nov 24 '22 at 08:10
  • 1
    No it's the new question which is meaningless, because `void*` has no layout and is not indexable and `ptr2[0]` is simply not valid. – n. m. could be an AI Nov 24 '22 at 08:19
  • @stefaanv I can't really come up with anything to make sense with the shown code. If it is taking a `void*` instead of a properly-typed pointer, it should be agnostic about the actual type, meaning it can't be modifying the values or deallocating without messing up the nested allocations. It also can't be reallocating for the same reasons. It could reassign the pointer without using the input value at all, but then why would the caller assign it first and why wouldn't it be a return value? Only I could think of is that this is a minor optimization to avoid separate input and output parameters. – user17732522 Nov 24 '22 at 08:23
  • Of course that is assuming that the shown signature is the actual signature of the function, without any additional parameters. – user17732522 Nov 24 '22 at 08:27
  • Next time you post, include output statements that have unexpected behaviour and explain what you expected instead. Then we can explain why the behaviour that happened happened and what you were doing wrong. As things stand nobody can really say what the problem is since you haven't indicated any concrete problem . – M.M Nov 24 '22 at 08:33
  • @user17732522 agreed that without additional parameters void* parameters in most cases don't make sense. The code in the question is simplified. With extra parameter, there are use cases possible where the pointer needs to be changed when using C-style code (`void**` in C) – stefaanv Nov 24 '22 at 09:34
  • @M.M, I came across this when hunting for memory leaks. The guy who's written the code is probably long gone by now and the output is an MFC application. This is just one out of a few hundred files. – justanotherguy Nov 24 '22 at 15:22
  • @justanotherguy regardless, it's still not really possible to say what might have been done wrong in "// whatever" and "// further operations here", without code posted. – M.M Nov 24 '22 at 21:13

1 Answers1

0

The question is pretty unclear but I will try to answer anyway:

  • casting pointers does not alter any memory layout
  • the layout of a pointer is some contiguous bytes (usually 4 or 8) that hold a memory address. This should not be confused with the layout of memory that a pointer may be pointing to.
  • the layout of the objects allocated by new int *[5] is 5 contiguous pointers each of type int *. Mucking around with other pointers that point to this memory block does not change the layout of this memory block.

Inside foo, the memory that was allocated by new int *[5] can be accessed by recovering its address with the correct type:

void foo(void*& ptr2)
{
     int **ptr = static_cast<int **>(ptr2);

     for(int j = 0; j < 5; j++)
         std::cout << ptr[j] << '\n';
}

Any other ways of attempting to access the memory block in question cause undefined behaviour. (Well, except for aliasing it as unsigned char).

M.M
  • 138,810
  • 21
  • 208
  • 365