cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
Showing results for 
Search instead for 
Did you mean: 

Community Tip - Learn all about PTC Community Badges. Engage with PTC and see how many you can earn! X

mcadincl.h - const error for input arrays ??

PhilipOakley
5-Regular Member

mcadincl.h - const error for input arrays ??

I have just been developing a user dll.

I have had an error that indicates that the mcadincl.h isn't as complete as it could be and/or there is a 'c' limitation not covered in the mathcad help guide.

The problem source is the classic user (me) mistake of only using a single '=' when a boolean comparison was required. I was input checking an options vector.
if ( !( (opt->hReal[0][OPT_LIN_CIRC] = 0.0) etc.
I thought it would be protected as other input variables are.

The problem is that the variable opt is an input array and should be imutable, a const. Unfortunately they are double referenced, so the constuct above allowed the checking code to overwrite the input data, something that is not supposed to be possible with the correct declarations and type checking.

Is there a right way to declare the input arrays in the mcadincl.h file so that input arrays are noted as fixed (immutable)?

Philip Oakley

Aside.

It is always better to code the comparison as (value == variable) so that when such mistakes happen the compiler flags the bad assignment!
i.e. (0.0 == opt->hReal[0][OPT_LIN_CIRC]) vs (0.0 = opt->hReal[0][OPT_LIN_CIRC])

Extract of mcadincl.h
// complex array type
typedef struct tagCOMPLEXARRAY {
unsigned int rows;
unsigned int cols;
double **hReal; // hReal[cols][rows], == NULL when the real part is zero
double **hImag; // hImag[cols][rows], == NULL when the imaginary part is zero
} COMPLEXARRAY;

// this is the complex array type received from mathcad
typedef const COMPLEXARRAY * const LPCCOMPLEXARRAY;
// this is the complex array type that should be returned to mathcad
typedef COMPLEXARRAY * const LPCOMPLEXARRAY;
19 REPLIES 19

Philip,

If you want to stick with straight c, I think this will handle the readonly issue you've got.

Add the following to your McadIncl.h file.

// ReadOnly complex array type
typedef struct tagReadOnlyCOMPLEXARRAY {
unsigned int rows;
unsigned int cols;
const double **hReal; // hReal[cols][rows], == NULL when the real part is zero
const double **hImag; // hImag[cols][rows], == NULL when the imaginary part is zero
} ReadOnlyCOMPLEXARRAY;

Then replace the definition of LPCCOMPLEXARRAY with this one.

// this is the complex array type received from mathcad
typedef const ReadOnlyCOMPLEXARRAY * const LPCCOMPLEXARRAY;

The compiler can then catch both modifications:
M->rows = ...
M->hReal[j][i] = ...

Robert
PhilipOakley
5-Regular Member
(To:radair)

Robert,
That (the update to the mcadincl.h) was just what I was looking for. I'll proably not go the C++ route just yet, but I'm sure that is of help to others.

Does the updated ReadOnly tag require 'const's on all the structure names and pointer indirections?

The **hReal pointer vs [i][j] indexing also looks fun to decypher!

I was expecting that we would/should protect all except the final returned value for returned variables...

I also raised this as a coding 'bug' with PTC tech support.



Philip Oakley

On 9/10/2009 9:14:42 AM, philipoakley wrote:
>Robert,
>That (the update to the
>mcadincl.h) was just what I
>was looking for. I'll proably
>not go the C++ route just yet,
>but I'm sure that is of help
>to others.
>
>Does the updated ReadOnly tag
>require 'const's on all the
>structure names and pointer
>indirections?
>
>The **hReal pointer vs [i][j]
>indexing also looks fun to
>decypher!
>
>I was expecting that we
>would/should protect all
>except the final returned
>value for returned
>variables...
>
>I also raised this as a coding
>'bug' with PTC tech support.
>
>
>
>Philip Oakley


Philip,

It probably should have const everywhere for consistency, here's a short blog on const http://blog.voidnish.com/?p=37, but my compiler was flagging M->hReal=0; as an error because I am then trying to modify M which already has the const value defined in LPCCOMPLEXARRAY.

Just to clarify moving to c++, you can vary as much or as little as you want from c. For example, you can use inline functions rather than macros to get type-safe compiler expansion and I personally like the ability to pass by reference whenever possible rather than by pointer because the code is easier to read and I don't have to worry about NULL pointers getting passed around. Going a step further and using c++ for smart pointers allows me to write a class that handles automatic memory cleanup (getting rid of memory leaks) and bounds checking (a real time saver when debugging) without modifying the c syntax for using arrays. Overall, these few modifications give me a big boost in productivity while reducing errors at the same time.

There's a nice little book called "More Effective C++" by Scott Meyers that covers things like smart pointers and why y = x+1; is slower for c++ classes than y=x;y+=1; - the reason being most compilers convert y = x+1; to temp = x; temp+=1;y=temp; I recommend it to people who have some basic working knowledge of c++ as it's fairly easy to read.

Robert
PhilipOakley
5-Regular Member
(To:radair)

>If any of that is of interest, let me know and
I'll go through and dig up some of my old
tutorials.

I'd be interested...


I have now added the const to all the terms in the
ReadOnly tags, and it works a treat. (did it for
the scalars as well)

I still had some old code ready for a second
function, and it picked up the errors in that.

Arguably the
"const double **hReal; // hReal[cols][rows], ==
NULL when the real part is zero "
should be properly defined as const array of const
arrays of const....


Philip Oakley


>Arguably the
>"const double **hReal; //
>hReal[cols][rows], ==
>NULL when the real part is
>zero "
>should be properly defined as
>const array of const
>arrays of const....
>
>
>Philip Oakley


Just to be clear, I think this is the const array of const... you mean

// ReadOnly complex array type
typedef struct tagReadOnlyCOMPLEXARRAY {
unsigned int rows;
unsigned int cols;
const double* const * const hReal; // hReal[cols][rows], == NULL when the real part is zero
const double* const * const hImag; // hImag[cols][rows], == NULL when the imaginary part is zero
} ReadOnlyCOMPLEXARRAY;

This also catches M->hReal[0]=0 which my previous definition didn't.

I'll look at pruning down some of my old stuff for matrices to make it more readable - no real need to know about matrix multiplication speed optimizations available with block matrices (cache optimization issues) just to get some of the benefits of c++ arrays.

Robert
PhilipOakley
5-Regular Member
(To:radair)

Magic. We got to the same place !

double const * const * const hReal; // hReal[cols][rows], == NULL when the real part is zero

read's as "hReal is a constant pointer to a constant pointer to a constant double. with comment Those pointers being used for 2d array indexing"

Noting that one can't do a dummy definition with hReal[][] which would have been nice, but c'est la vie.

Philip Oakley
PhilipOakley
5-Regular Member
(To:PhilipOakley)

On 9/10/2009 6:44:30 PM, philipoakley wrote:
>Magic. We got to the same
>place !
>
>double const * const * const
>hReal; // hReal[cols][rows],
>== NULL when the real part is
>zero
>
>read's as "hReal is a constant
>pointer to a constant pointer
>to a constant double. with
>comment Those pointers being
>used for 2d array indexing"
>
>Noting that one can't do a
>dummy definition with
>hReal[][] which would have
>been nice, but c'est la vie.
>
>Philip Oakley

... probably using an extrn definition - need to check if it can be applied through a struct though...

Philip Oakley
PhilipOakley
5-Regular Member
(To:PhilipOakley)

The coding is now working OK, though I did hit another 'error/omission' and was pondering a C language problem.

The documentation isn't good (non-existent) about additional user arrays, managed on the same basis as MathCAD arrays.

The arrays listed in the function call are created by mathcad (well actually the structure that handles the information about the array). If the user wants an additional temporary array, handled in the same manner, then the user has to create both the structure and the pointer to the structure.

COMPLEXARRAY Ystruct; // we have to create our own structure. This one is extra to those passed as parameters.
LPCOMPLEXARRAY Y = &Ystruct; // Now create the pointer to the structure, so we can use it just like the other mathcad arrays.


On the C language side, C gets itself into an 'elephant in the room' problem with 2d and multidimensional arrays (as far as I can see).
If the array is defined within the local code, the books suggest that the location Array[col][row] will be accessed as *(Array + row*rows+col). KRI sect 5.9 p113.

While a 'proper'(?) 2d double indirect array, as defined externally by mathcad uses *( *(Array+row) +col).

Confusing eh.

Philip Oakley
PhilipOakley
5-Regular Member
(To:PhilipOakley)

On 9/14/2009 4:51:45 AM, philipoakley wrote:
>suggest that the location
>Array[col][row] will be
>accessed as *(Array +
>row*rows+col). KRI sect 5.9
>p113.

should read
*(Array + (row x cols) +col).

using x as multiply!
getting the row length correct.
remembering C indexes along the rows fastest, so these are C based row/col indices,

Philip Oakley

PhilipOakley
5-Regular Member
(To:radair)

Robert, for the 1d indexing I'd expect modern
processors and compilers to be fast either way, as
you say.

It was the 2d access that got confused.

x = Array[7][4];

should be compilable without knowing the
size of Array, if the access is through
pointer to pointer etc.

It is mainly an omission in the documentation
about how array names decay to pointers, but then
the second index fails to decay recursively.

In the Mcadincl.h it had to say that Array was a
** to guide the indexing (rather than just say it
was an Array[][] with empty size values). It
wasn't possible to say it was extern either
because of the clash with the typedef 😉

Philip Oakley

PhilipOakley
5-Regular Member
(To:radair)

C indexing is a mess, always has been. It's the only language I know of where subscripting is commutative -- A[i] has exactly the same meaning as i[A]. Write it that way if you really want to confuse any readers of the code.

Then there is the disconnect between the concept of indexing and C's implementation. C does not index arrays but pointers. To make code that looks sort of reasonable C takes most references to array names and treats them as pointers to the first element of the array (rather than the actual array). There are some, not well documented, exceptions to this, where an array name does refer to the actual array. sizeof is an example.

The construct A[i][j] can be applied with various declarations, with somewhat different meaning. To understand it you have to first break it down into two separate subscripting operations, read it as (A[i])[j]. For this to work A must be a pointer and A[i] must be a pointer. A can be declared as an array, which make A (logically) mean &A[0]. For the second subscript to work A must be a pointer to a pointer. Again, the promotion of arrays to pointers means that A could be an array of pointers, or, since it's all recursive, and array of arrays.

So you could have real **A, a pointer to a pointer to a real, or you could have real A[m][n] (where m has to have a compile time integer values, n is optional) which is an array of arrays. When used, A becomes a pointer to an array which becomes a pointer to a pointer.

To avoid the problems with m not being known at compile time Mathcad passes the arrays as a pointer to an array of pointers to arrays of reals (the columns of the matrix). While I believe that the actual data occupies a contiguous set of memory locations, this is not required by the interface, and part of the overhead in passing an array to a user DLL is constructing the pointers to the columns (I doubt that Mathcad normally keeps that structure for its own use.
__________________
� � � � Tom Gutman

Philip,

Tom sums up the confusion pretty well. Using the notation M[col][row] tells the compiler nothing about the length of the row in each column. For matrices, it is implicitly understood that each row is the same length.

The attached vc6 project shows how a 2D array with different numbers of rows in each column can use the same notation as a 2D matrix and fill up a contiguous block of memory. The compiler knows nothing about either how many rows or columns are present in the array. It is up to the programmer not to make a mistake - hence the issue of bounds checking is left to a human, not a compiler.

If you want the compiler to do bounds checking on a 2D matrix, you have to give it the bounds at compile time. So, for example

double M[2][4]; is ok because because the compiler can now allocate memory and check for out of bounds conditions.

double M[][]; fails because the compiler doesn't know how to initialize M. You need pointers for dynamic allocation. That's why Mathcad is passing pointers and telling you how many rows and columns are present in the array.

Passing M in a function allows syntax such as
void f(double M[][4]){}
but you still need to tell the compiler the number of rows in the matrix or it will complain that it doesn't know how to compute the pointer offsets. Any unspecified array bounds can still be filled in by the programmer with any old number to produce out of bounds errors, so you're not buying much protection using notation that only fills in one of the bounds.

Robert
PhilipOakley
5-Regular Member
(To:radair)

On 9/15/2009 3:12:12 PM, radair wrote:
>Philip,
>
>Tom sums up the confusion
>pretty well. Using the
>notation M[col][row] tells the
>compiler nothing about the
>length of the row in each
>column.
...
>
>double M[][]; fails because
>the compiler doesn't know how
>to initialize M. You need
>pointers for dynamic
>allocation. That's why
>Mathcad is passing pointers
>and telling you how many rows
>and columns are present in the
>array.
...
>
>Robert

Agree with everything you said. The bit I was
grumping about was the inability to use in the
mcadincl.h file a methods of statement about the
MathCAD style arrays that actually used the array
notation, rather than having to shift to the
pointer notation.

This is made worse because it is buried in a
typedef so you can't even use an extern (it's an
either/or according to KRI)

As noted, in the mcadincl.h file we are not
actually attempting to assign any memory anyway,
just indicate that we are indexing a 2d array...

grump over..

Philip Oakley

PS
In the Visual C++ Express edition, which of the ancillary files are necessary for a zip so other
can rebuild. I was going to drop some of the space
wasters..
PhilipOakley
5-Regular Member
(To:PhilipOakley)

Just to note that Radair and I are working on an
improved file and supporting C and C++ capabilities.

Philip Oakley
PhilipOakley
5-Regular Member
(To:PhilipOakley)

After some extended discussion with collaborators
the following has been identified as a suitable
amendment.
This provides an additional typdef
tagReadOnlyCOMPLEXARRAY for the protected arrays,
and then uses it in (changes) the LPCCOMPLEXARRAY
definition.

My apologies for the delay in writing this up. A
library of C++ function templates was also
written, avoiding many memory management issues
and simplifying coding.

I have also posted this on the PTC software
problem call tracker (maintenance)

Philip

// complex array types
// Note : C does not allow a typedef to create a
generic 2d array, rather a pointer pointer must be
used.
// When used to access an array passed by mathcad
all these pointers are protected from change using
the const keyword .
// Note the additional C prefix in LPC to 'long
pointer constant' items, particularly to COMPLEX
data.

typedef struct tagReadOnlyCOMPLEXARRAY {
const unsigned int rows;
const unsigned int cols;
double const * const * hReal; //
hReal[cols][rows], == NULL when the real part is
zero
double const * const * hImag; //
hImag[cols][rows], == NULL when the imaginary
part is zero
} ReadOnlyCOMPLEXARRAY;

typedef struct tagCOMPLEXARRAY {
unsigned int rows;
unsigned int cols;
double ** hReal; // hReal[cols][rows], ==
NULL when the real part is zero
double ** hImag; // hImag[cols][rows], ==
NULL when the imaginary part is zero
} COMPLEXARRAY;

// this is the complex array type received from
mathcad
typedef const ReadOnlyCOMPLEXARRAY * const
LPCCOMPLEXARRAY;

// this is the complex array type that should be
returned to mathcad
typedef COMPLEXARRAY * const LPCOMPLEXARRAY; //
const

Philip Oakley
Top Tags