C++ Question Help using a double pointer

Zatnikitelman

Addon Developer
Addon Developer
Joined
Jan 13, 2008
Messages
2,310
Reaction score
5
Points
38
Location
Atlanta, GA, USA, North America
In my latest project I've come across the need to store a dynamically allocated array of char pointers such that chararray[0] = "some string", chararray[1] = "some other string" etc. A double pointer seems appropriate here, but I'm having some problems making it work.
In the .h file, I have:
Code:
char **paylist; //declare the double pointer
Then in the .cpp file, In clbkLoadState, I'm using this first to dynamically allocate the array, then to store stuff to it:
Code:
else if(!_strnicmp(line, "PAYNUMBER", 9))
{
  sscanf(line+9, "%d", &size);
  paylist = new char *[size]; //dynamically allocate the array
}
else if(!_strnicmp(line, "PAYLOAD", 7))
{
  if(index < size)
  {
    sscanf(line+7, "%s", paylist[index]
    index++ //increments the index variable to allow for multiple PAYLOAD definitions
  }
}
For some reason, it always executes the second "else if" statement twice, but never more than twice before crashing. The scenario file it's reading from is correct in its syntax. The size variable is also correct (checked by writing to Orbiter.log). And yes, I AM deleting paylist in the destructor.
Thanks for any help,
Zat
 

dbeachy1

O-F Administrator
Administrator
Moderator
Addon Developer
Donator
Beta Tester
Joined
Jan 14, 2008
Messages
8,846
Reaction score
793
Points
203
Location
VA
Website
alteaaerospace.com
The problem is that you are not actually allocating any memory for the actual strings you are storing -- all you are allocating so far is memory for the array of pointers. You need to actually populate each pointer in that array with the address of a valid memory block to actually contain that string's bytes. For example:

Code:
else if(!_strnicmp(line, "PAYLOAD", 7))
{
  if(index < size)
  {
[COLOR="Red"]    // allocate memory for the string (line) we are about to read
    paylist[index] = new char[128];  // you may need to increase this size depending on the maximum line length of this parameter in the scenario file
[/COLOR]    sscanf(line+7, "%s", paylist[index]);
    index++ //increments the index variable to allow for multiple PAYLOAD definitions
  }
}

EDIT:
To make this clearer, here is how the memory looks:
paylist[0] -> string #1 (128-byte memory block #1)
paylist[1] -> string #2 (128-byte memory block #2)
...etc...

One more thing: be sure to eventually free each memory block you allocate (usually done in your vessel's destructor). For example:

Code:
for (int i=0; i < size; i++)
  delete paylist[index];   // free this particular string

delete[] paylist;  // free space for the pointer array

This is another instance where stepping through the code in a debugger is massively useful in figuring out why a CTD occurs. :)
 

dumbo2007

Crazy about real time sims
Joined
Nov 29, 2009
Messages
675
Reaction score
0
Points
0
Location
India
Is it possible to start Orbiter under the VC++ debugger ? I would love to step through the code for my vessel modules without relying on the oapidebugstring thing all the time.
 

Zatnikitelman

Addon Developer
Addon Developer
Joined
Jan 13, 2008
Messages
2,310
Reaction score
5
Points
38
Location
Atlanta, GA, USA, North America
Thank you SOO much debeachy1!! I'm still very much a beginning C++ programmer and pointers are definitely not one of my strong areas [yet]!

@dumbo2007, yes it is, I can't remember the exact procedure, but I know it involves copying some files into the Modules directory (beyond your module), then while Orbiter is open, attach your module to the Orbiter process in VS2008 and step through it that way.
 

dbeachy1

O-F Administrator
Administrator
Moderator
Addon Developer
Donator
Beta Tester
Joined
Jan 14, 2008
Messages
8,846
Reaction score
793
Points
203
Location
VA
Website
alteaaerospace.com
Is it possible to start Orbiter under the VC++ debugger ? I would love to step through the code for my vessel modules without relying on the oapidebugstring thing all the time.

Yes, here's how to do it in VS 2005 and VS 2010 (I assume it is similar for VS 2008):

1. Open your DLL project properties in Visual Studio and select configuration "Debug".
2. Expand Configuration Properties and click on Debugging.
3. Set C:\Orbiter\Orbiter.exe for Command (change path to match your Orbiter instance). (Note that VS 2005 does not require the full path here, but VS 2010 does.)
4. Set C:\Orbiter (or wherever else your Orbiter instance is running) as Working Directory. (Make sure "Attach" is still set to No.)

(Of course, you will need to be sure to copy your add-on's DLL to your C:\Orbiter\Modules directory. You can automate that by setting a Post-Build Event under Build Events.)

To test your configuration:

1. Put a breakpoint inside your vessel's constructor or one of the callbacks, like clbkSetClassCaps. You can do that by moving the cursor to the line you want and then setting a breakpoint via the menu, a hotkey (usually F9), or by clicking in the far-left edge of the window on that line. A little red circle should appear on the left edge when the breakpoint is set.

2. Run the debugger. You will see an "Orbiter.exe was not built with debug information" warning, but that is normal since you're debugging Orbiter.exe. Just click continue and let Orbiter load under the debugger. What you are debugging here is your DLL, not the Orbiter core.

3. When your vessel loads the debugger should pop up when your breakpoint is hit. Then move the mouse over a few local variables in your method and see if the values pop up. If you can see that, your debugger is working! At this point you can now single-step through your code and examine variables, etc. The debugger will also halt if you get a CTD, allowing you to see exactly where the CTD occurred and the state of your variables and objects at the time of the crash.

4. Congratulations! It will now be much easier to create bug-free add-ons! :)
 

dumbo2007

Crazy about real time sims
Joined
Nov 29, 2009
Messages
675
Reaction score
0
Points
0
Location
India
Wow.....this is really important info......perhaps it can appear in the wiki as well under the compiler setup page.

Thanks!
 

orb

O-F Administrator,
Administrator
Joined
Oct 30, 2009
Messages
14,023
Reaction score
0
Points
0
(Of course, you will need to be sure to copy your add-on's DLL to your C:\Orbiter\Modules directory. You can automate that by setting a Post-Build Event under Build Events.)
Instead of "Post-Build Event", I set "Output File" in Linker » General, and it works fine under VS2005 and VS2008.
 
E

ex-orbinaut

Guest
I will take advantage of this thread, with no intention of any hijacking. On the contrary, I think it might be good supplementary information to the thread starter, as well as answering my own query, as it relates to the same sort of thing.

This is a problem that was solved before with the STL vector library, but I still have problems making it work the old fashioned way. Basically it involves passing a multi dimensional array of a structure to a function.

If it is done this way, it works....


Code:
const int SIDES = 2;
const int SHIPS = 10;

struct my_struct
{
    int x;
    int y;
    int z;
};

int main()
{
    my_struct ship[SIDES][SHIPS];

    int i;
    for(i = 0; i < SIDES; i++) 	
        set_pos(ship[COLOR="DarkRed"][i][/COLOR]);

    _kbhit();
    return 0;
}


void set_pos(my_struct *st)
{
    int j;
    for(j = 0; j < SHIPS; j++)
    {
        st[j].x = (rand() % 20000) - 10000;
        st[j].y = (rand() % 20000) - 10000;
        st[j].z = (rand() % 20000) - 10000;
    }
}

That is to say, passing the first dimension to the function and allowing the function to manage the second in a for-next loop. But I cannot get it to work passing BOTH dimensions at once to the function. Here´s one example of what I have tried...


Code:
const int SIDES = 2;
const int SHIPS = 10;

struct my_struct
{
    int x;
    int y;
    int z;
};

int main()
{
   my_struct ship[SIDES][SHIPS];

   set_pos(ship);

   _kbhit();
   return 0;
}


void set_pos(my_struct **st)
{
    int i, j;
    for(i = 0; i < SIDES; i++)
    {
        for(j = 0; j < SHIPS; j++)
        {
            st[i][j].x = (rand() % 20000) - 10000;
            st[i][j].y = (rand() % 20000) - 10000;
            st[i][j].z = (rand() % 20000) - 10000;
        }
     }
}

I imagine that this is wrong because of the fact that I am not really passing a nested structure, which is what I believe the ** would make the function look for. Just putting "st" in the function parameter does not work either. So, I am now utterly confused, as I dont believe I can use & and * together (makes no sense to reference and dereference at the same time, even though it looks like that is what it wants, to me).

What is the trick, please? Any help greatly appreciated...

My thanks for your kind attention.
 
Last edited by a moderator:

MeDiCS

Donator
Donator
Joined
Sep 22, 2008
Messages
605
Reaction score
0
Points
0
[...]
set_pos(ship);
[...]
I think you mean set_pos(ship)?

I imagine that this is wrong because of the fact that I am not really passing a nested structure, which is what I believe the ** would make the function look for. Just putting "st" in the function parameter does not work either. So, I am now utterly confused, as I dont believe I can use & and * together (makes no sense to reference and dereference at the same time, even though it looks like that is what it wants, to me).
No, it's right. An n-dimentional array is equivalent (but not strictly equal) to a n-nested pointer to pointer. Both lines are equivalent:
Code:
char a[1][2];
char **b;
The only practical difference is that a have it's space allocated at compile-time, and b needs to use either malloc or new. You can pass both a and b to any function that requires a char **.

If it's not compiling, what's the error you're getting? You may need to manually cast ship to my_struct **, but it will work.

---------- Post added at 12:56 PM ---------- Previous post was at 12:48 PM ----------

If you really want to do 'old-style' (pure C), you need to either use struct my_struct instead of only my_struct, or typedeffing it.
 
Last edited:
E

ex-orbinaut

Guest
I think you mean set_pos(ship)?


Yes, I did. Thanks for the heads up...

If it's not compiling, what's the error you're getting? You may need to manually cast ship to my_struct **, but it will work.

Indeed, that is the only error (type re-cast). So I just go ahead and typecast it and away it goes? Sorry, did not try that, but will now. My thanks for your help.

:tiphat:
 

Enjo

Mostly harmless
Addon Developer
Tutorial Publisher
Donator
Joined
Nov 25, 2007
Messages
1,661
Reaction score
5
Points
38
Location
Hildesheim, Germany
Website
www.enderspace.de
If you want to pass arrays as arguments, it's better to declare them not as tab[h][w], but tab[h*w], thus really creating a one dimensional array in disguise

take a look at this code snippet:

Code:
#include <iostream>

const int h = 10, w = 5;

void f( int * tab )
{
  for (int i = 0; i < h; ++i)   // don't confuse h with w. i is a row, not column
  {
    for (int j = 0; j < w; ++j)
    {
      tab[i * w + j] = i * j;   // ! Note calculating the index
    }
  }

  for (int i = 0; i < h; ++i)
  {
    std::cout << i << " = ";
    for (int j = 0; j < w; ++j)
    {
      std::cout << tab[i * w + j] << "\t"; 
    }
    std::cout << std::endl;
  }
}

int main()
{
  int tab [h*w];
  f(tab);
  std::cout << "Hello world!" << std::endl;
  return 0;
}
This way you loose the easiness of using [j], but you can do anything you want with the array now.

2nd pro of this is that AFAIK it's the only easy way of dealing with dynamically allocated arrays. Imagine having to populate each newly declared pointer with its child pointers and then deleting all the children and then the parent. Sounds wacky.

I'm working with image recognition at my job now, and I capture pictures taken by camera. They are given in the very same format - a one dimensional array, which has w*h elements. It's worth knowing the presented technique of dealing with arrays.
 
Last edited:

dbeachy1

O-F Administrator
Administrator
Moderator
Addon Developer
Donator
Beta Tester
Joined
Jan 14, 2008
Messages
8,846
Reaction score
793
Points
203
Location
VA
Website
alteaaerospace.com
Obviously there are different ways to do this, but here's how you would pass the two-dimensional my_struct array to set_pos:

Code:
// 'st' is a pointer to our array here
[COLOR="Red"]void set_pos(my_struct st[][SHIPS])  [/COLOR] // first dimension may be omitted here
{
   // ...
}

// this method is unchanged 
int main()
{
   my_struct ship[SIDES][SHIPS];

   set_pos(ship);  // passes a pointer to 'ship' array of structures here

   _kbhit();
   return 0;
}

A two-dimensional array is really just a big block of memory like Enjo said above, which is passed around as a pointer, so it all comes down to how you want to get that pointer to your method and use it.

For more information on two-dimensional arrays, take a look here.
 
E

ex-orbinaut

Guest
Thank you all so much. Not only is the problem finally solved, but my understanding of this particular aspect of C programming suddenly falls neatly into place for once and all. It was in seeing the alternative methods to do the same thing that it "clicked" for me. Once again, OF expands my comprehension...

Excellent.

:thankyou:


PS:
As a footnote, I will add that I had tried this...

Code:
// 'st' is a pointer to our array here
void set_pos(my_struct st[][SHIPS])   // first dimension may be omitted here
{
   // ...
}

...but this way...

Code:
void set_pos(my_struct st[][])

You can be SO close and still SO wrong.

Thanks again.
 
Last edited by a moderator:
Top