﻿ORIGINAL STATEMENT OF THE PROBLEM:
 
Challenge 007
created by: Jose Luis De la Cruz Lazaro
			contact@theworldofchaos.com
			www.theworldofchaos.com
Date: September 22, 2000

This program is a recreation of a problem I had some time ago.

The objective is to add 2 matrices by means of a function Sum, where the matrices are two-dimensional arrays created dynamically and their order is entered from the keyboard.

But it turns out that the program does not show errors when compiling, maybe some warnings, but at the moment of executing the exe, some of the following cases happen:

- The program hangs from the start;
- It shows well the result, but at the end it hangs;
- It shows the result well, but at the end it shows the error message: Null pointer assignment.
-  The program displays the result well, but only for small order matrices;

Questions
1st. Find where the error is and explain why it crashes. You do not want to give the solution for a specific compiler, I say this, because your solution can run in DJGPP, but not in Borland C. What you want is that the solution is general.

2nd. Modify the program in such a way that you can add the 2 matrices more efficiently.

3rd. Up to what order (of the matrices), could the program work well, without crashing due to lack of memory? Explain why.

It must be fulfilled that the solution of question 1 must agree with what is said in question 3.
said in question 3.

SOLUTION:
Date: Solved on October 11, 2000

QUESTION 1:

The first thing you want is to find where the error is and
explain why it crashes. You don't want the solution to be given for a
specific compiler, I say this because your solution can run on DJGPP,
but not on Borland C. You want the solution to be general.

SOLUTION:

In this case you can't talk about the error, but about the errors. First
of all, let's start with the definition of double pointers and ask the
question: is it feasible to define double pointers to create dynamic
two-dimensional arrays? Look at the following declaration, which is
repeated very often in the program:

int **A;

To better understand how **A works, let's analyze the first asterisk:
*A is a one-dimensional array with no length, for convenience
let's call it p = *A, now p is a one-dimensional array. Let's now analyze the
second asterisk: **A according to our simplified notation we can
write it as *p, which clearly shows that *p would be a
collection of one-dimensional arrays of equal length, that is, it is a
two-dimensional matrix, and p will now be a pointer to the rows of said
matrix, as simple as that. It is the same idea as a simple pointer, but now
instead of pointing to a collection of variables, it points to a
collection of arrays.

Now going back to the original variable p=*A, then *A is the pointer to
the rows of the matrix A, according to the usual notation, so to access
the "i" row of the matrix A, it would be something like: *(A+i) or using the
array notation it would simply be A[i].

So A[i] is a pointer to the row "i" of the matrix, which has no
length, because the declared pointers do not point anywhere, so they need to be assigned a memory address to point to.
The latter is done by the CreateMatrix function. Now let's look at its
declaration:

void CreateMatrix( int **A, int Order)

{

for( int i=0; i<Order; i++)

A[i] = new int[Order];

}

In this function, row i is assigned a length equal to the order of the
matrix ("Order"), this process must be repeated "Order" times, because the
matrix is ​​square, so it must have "Order" rows, that is, at the
end all the elements of the matrix have a reserved space in memory.

So far so good... but the error comes here, in a HUGE oversight...
but what happens with the pointers that were not initialized with new? that is, all the A[i] for i>Order, the most logical thing is that they do not point to anything,
that is, they are null (NULL), but it is not so, they do point to some
region of memory, that is, they contain garbage, this garbage is sometimes so
important that if even 1 bit is changed, due to improper manipulations, the
program hangs, that is, it is interrupted and no longer follows its normal
procedure, it can even render the machine useless and force us to
restart it... Now let's look at this declaration:

int **Sum( int **A, int **B, int Order );

This function returns a double pointer, which is used like this:

a=Sum(a, b, N);

There is a serious error here because the Sum function, although it returns the
pointers to the sum of the matrices a and b, for the reasons stated above
it also returns the unused null pointers, which when assigned
to another double pointer, lose their initial content. Summarizing what I
want to say is that there is an assignment of null pointers from the
"Sum" function to the variable "a", which causes the program to crash. This is
the explanation of why the message "Null pointer assignment" appears.

I think it was a rather long introduction, but we must remember that the
objective is to learn... Now the way to solve this problem would be
modifying the Sum function in such a way that it does not return any value, and
that it updates the pointer "a" by reference, that is, directly modifying
the content of "a" in the Sum function, this would be like this:
 
  
 
 void Sum( int **A, int **B, int Orden )
 
 {
 
 int i,j;
 
 for(i=0;i<Orden;i++)
 
 for(j=0;j<Orden;j++)
 
 A[i][j]=A[i][j]+B[i][j];
 
 }
 
 What is done here is to directly modify the content of A and
assign the sum to it, and since A is a pointer, then it is passed by
reference in the Sum function, as shown below:

Sum(a, b, N);

Then the variable "a" is passed as a reference, so that at the end it
will have as content, the Sum of a and b.

Clarifications:

-A variable "x" passed by reference to a function, means that the
function instead of taking as argument the content of "x", takes the
address of "x" = &x, now any change that is made to the
reference within the function, will be suffered by the variable "x" outside the
function.

-A pointer is always passed as a reference in the arguments of a
function.

But now a new, more serious problem arises, and that is that the declaration "int
**A;" creates an unknown number of rows, which we never initialize,
I'm not referring to the content of the rows "*A", that is already initialized,
but to the number of rows that our matrix will have, that is, how many
pointers to the rows we need, this is equivalent to as if
we worked with a pointer without initializing it first with new, we delete
valuable garbage from the pointer to put our data, and this harms the
stability of the program that at any moment crashes.

The solution to this new problem would logically be restricting the
number of rows in matrix A to a maximum static number of rows, I say
static, because all the variables declared statically contain
non-valuable garbage, which if deleted does not harm the stability of the
program at all. To clarify the idea, let's look at the following
code:

const int MAX = 126; //Defines the maximum order of the matrices

...

...

int *a[MAX],*b[MAX];

With this I restrict the matrices to have a "MAX" number of rows (in
the solution to question 2 I will explain why MAX = 126), but now it will be
thought that the matrix "a", being declared statically, takes up a place
in memory, but it does not, it does not take up any space, because the only thing I did was
declare a "MAX" number of null pointers, and as we know a null or uninitialized
pointer does not take up space in memory. To initialize the matrix
"a", we will proceed as before, only the CreateMatrix function is
altered slightly as follows:
 
int CreateMatrix(int **A, int Orden)
{
	for (int i = 0; i < Orden; i++)
	{
		A[i] = new int[Orden];
		if (A[i] == NULL || Orden > MAX)
		{
			cout << "\no memory to create array...";
			return 0;
		}
	}

	return 1;

}
 
Only a small change has been made to detect if there is no memory to
create a new row, since new returns NULL if there is not enough memory
to assign to the matrix, in which case the function returns zero, which
will cause the program to abort. The entry of a matrix order greater than
"MAX" is also restricted, since in this case the number of declared rows would
be exceeded, and this would obviously produce an error.

NOTE: you can check this by running the solution to the [Challenge] (See file
juez0007a1.cpp )

QUESTION 2:

Secondly, modify the program in such a way that the 2 matrices can be added
more efficiently.

The way to proceed to solve the problem in question 1 is
good, but it has certain disadvantages, such as not declaring dynamic two-dimensional
arrays, since it always depends on the static variable MAX.

So one way to declare a two-dimensional array (Matrix) of order N dynamic, is to simulate it as a vector (one-dimensional array) of length N*N, where it can be easily observed that said vector will be composed of N vectors of length N, where vector 1 will represent row 1 of the matrix, vector 2 row 2 and so on until vector N represents row N of the matrix.

So to declare a matrix A of order N, you would proceed as follows:

int *A;

A = new int[N*N]; //A has length N*N

It's that easy, but you have to be very careful that N*N does not exceed the
maximum value allowed for its variable type. For example if N is int
then N*N < 32768.

To access the element (i,j) of the matrix you would only have to do the
following:

A[j*N+i]; //element (i,j) of the matrix

It should be noted that this method is widely used to work with images
and to process them in memory. For example, in a game a sprite (graphic) is a two-dimensional matrix that contains the
colors of the image.

Applying this method to the problem and now using a Matrix structure, to create a Matrix data type, which will have a pointer as a member
variable, we obtain an optimized program that uses dynamic two-dimensional
arrays, which was what was wanted from the beginning.]

NOTE: you can check this by running the optimization on the [Challenge] (See
file juez0007a2.cpp )
 
  QUESTION 3:

The third question is: Up to what order (of the matrices) could the program
work well, without crashing due to lack of memory?
Explain why.
The solution to question 1 must agree with what was said in
question 3.

SOLUTION:

In question 1, we said that the number of rows (equal to the order of the
matrix because it is square) must be a "MAX" number, which is
unknown, as what they are asking me is that the matrices "a" and "b" work
with their highest possible order before the program crashes, then
that means that we must work with all the possible memory that we
have. But here comes the question: how much available memory do I
have? First we must know that there are 6 memory models
available, which we can see in the following table:
 
+--------+----------------+
¦ Memory ¦     Segments   ¦
¦ Model  ¦ Code¦Data¦Stack¦
+--------+-----+----+-----+
¦ Tiny   ¦      64 K      ¦
+--------+----------------+
¦ Small  ¦ 64 K¦   64 K   ¦
+--------+-----+----------+
¦ Medium ¦ 1 MB¦   64 K   ¦
+--------+-----+----------+
¦ Compact¦ 64 K¦   1 MB   ¦
+--------+-----+----------+
¦ Large  ¦ 1 MB¦   1 MB   ¦
+--------+-----+----------+
¦ Huge   ¦ 1 MB¦64 K¦64 K ¦
+--------+-----+----+-----+
 
 
 What we need to know is that, in any compiler that we use, the default memory model is Small (Small Model), where the table shows that the code (executable binary) must occupy at most 64K and it is also observed that the data (portion of memory
where all the static and global variables that we declare in our program are stored) plus the stack (same as the data but only for
local variables) must occupy 64K.

Returning to the problem, the matrices "a" and "b" are part of the program data, so between them they can be 64K at most, so to find the maximum order we would have to do the following calculation:

Size(a) + Size(b) = 64K bytes ...(1)

where:

sizeof(int) = size in bytes that an integer type occupies in memory =
normally it is 2 bytes

Size(a) = (Number of elements of a)*sizeof(int) = MAX*MAX*2

Size(b) = (Number of elements of b)*sizeof(int) = MAX*MAX*2

Then replacing these values ​​in (1):

MAX*MAX*2+MAX*MAX*2 = 64K

from which clearing MAX and truncating its decimal part gives MAX = 126;

This is not true in practice because the theoretical 64K are never available
for data only, sometimes the stack removes a few bytes from the
data, the correct thing would be:

MAX*MAX*2+MAX*MAX*2 + size( stack ) = 64K

from which approximately we have:

MAX*MAX*2+MAX*MAX*2 = 60K

so MAX = 120

That is to say that in practice we will end up with a maximum order
of around 120 instead of 126.

NOTE: you can check this by running the solution to the [Challenge] ( See file
juez0007a1.cpp )

FINAL NOTE:

Well I hope I have not tired you with such a long solution, but I remind you
again that the objective of the Challenge is to learn...

Regards

       o  o Jose Luis De la Cruz Lazaro   o   220KV of Chaos
     o       o  Visit my homepage:          o      o
   o    o o    o THE WORLD OF CHAOS           o   o o
  o   o  o     o   https://www.theworldofchaos.com   o  o
  o    o     o                                   o    o  o
   o     o o     Chaos = Chaos & math ? C++ : ++C;        o
     o                                                     o
        o  o  o o o  FRACTALS UNLIMITED ooo o  o  o  o  o   o
                    o  o   o   o   o   o
     o               o   o   o   o   o
   o   o      o
 o      o   o  o  o  o  o o oooo      Yacsha Software & Desing
                                 O  O  o ooo Lima - Peru ooo o o O  O

