Solved

C Pointer to Pointer to Struct Array Help

Posted on 2011-02-17
9
543 Views
Last Modified: 2012-05-11
Hi Experts,

I am needing a data table format to hold results from a sql server. I am using ODBTP, which is a client-server network library to bridge systems that do not have odbc on them. I have this working, but if you see code related to it, then that is the story.

I have found some examples but I am not sure how to implement them. I was using the example here to formulate the possibility that my needs would be met: http://c-faq.com/~scs/cclass/int/sx9b.html

Basically I need rows of row of column structures, just as it would be found in a database. What I am not sure of is:

1) where do I use malloc to dimension the rows portion of the array?
2) may I pass a null pointer ("oColumns **table") to my function ("static void oExecute")? I will not know how many rows or columns there will be until oExecute has completed.
3) How do I calculate the size of malloc for a structure? do I use sizeof(**table)?
4) Can I just simply use the pointer to **table in my oReader function?
5) In my calling function which is not listed, how should I create oMetadata and the pointer metadata before passing the pointer to "oExecute"?

Thanks for your help!!



static void oExecute (const char* sql, oColumns **table, oMetadata *metadata)
{
	/*
		ODBTP sql execution function
		returns void
		accepts sql as string
		accepts pointer var table of oColumns struct type
		accepts pointer var metadata of OMetadata struct type 
	*/
	// variables
	odbHANDLE hCon, hQry;
	// get the config data into//ap_rprintf(r, "MESSAGE: %s\n", odbGetResponse( hQry ));
	//--> variables to be put into our metadata collection
	char *errors; // any errors that occur go here
	bool isError = false; // if no errors occur then keep at false
	size_t rows = 0, cols = 0;	// row count and column count
	// sanity checks
	if (!strlen(sql)){errors = "SQL String must not be zero length!"; goto complete; } // sql string must be valid
	// initalize odbtp network library
	if ( !odbWinsockStartup() ){ errors = "Could not initiate network transfer."; goto complete; }

	// check that we can connect
	if( !(hCon = odbAllocate(NULL) ) ) { errors = "Could not allocate connection"; goto complete; }
    if( !odbLogin(hCon, SQL_SERVER, 2799, ODB_LOGIN_NORMAL, SQL_STRING )) { errors = "Could not login"; goto complete; }
    if( !(hQry = odbAllocate( hCon ) ) ) { errors = "Could not allocate query"; goto complete; }
	if( !odbExecute( hQry, sqlstring ) ){ sprintf(errors, "Could not execute query: %s", odbGetErrorText( hQry )); goto complete; }
	// check for data --> no data does not imply an error
	if ( odbNoData( hQry ) )
	{ 
		// copy error buffers
		sprintf(errors, "%s", odbGetErrorText( hQry ));
		// check if there were any errors
		if (strlen(errors) && errors != "(null)")
			isError = true;
		// exit
		goto complete; 
	}
	// there is data so pass the data handle to oReader
	rows = odbGetRowCount( hQry ); // row count
	cols = odbGetTotalCols( hQry ); // column count
	table = malloc(rows * sizeof(int *));
	// logout
	if( !odbLogout( hCon, FALSE ) ) { errors = "Could not logout"; goto complete; }
	//---> Exit Procedure
	complete:
		// determine if the isError flag is set or there is any data in errors
		if (strlen(errors) || isError)
			isError = true;
		/* set metadata members */
		metadata->isError = isError; // set bool isError flag
		metadata->rowcount = rows; // set row count
		metadata->colcount = cols; // set column count
		metadata->message = errors; // set errors
		/* clean up */
		odbFree( hCon ); // free odbtp connection
		odbWinsockCleanup(); // clean up network connection
		/* return */
		return;
}

Open in new window

static void oReader (odbHANDLE hQry, oColumns **table, size_t rows, size_t columns)
{
	/*
		ODBTP sql reader function
		returns void
		accepts query handle
		accepts pointer var table of oColumns struct type
	*/
	// variables
	size_t			i;
	short int		colType;
	double			colLen;
	char			*colName;
	char			*colData_s;
	double			colData_n;
	float			colData_f;
    odbPVOID		pData;
    odbPTIMESTAMP	pts;
	// sanity checks
	if (!columns || !rows) return;
	// loop over rows
	while( odbFetchRow( hQry ) && !odbNoData( hQry ) ) 
	{
		// loop over columns
		for (i = 1; i <= columns; i++)
		{
			// reset variables
			colData_s = "";
			colData_n = 0;
			colData_f = 0;
			colType = 0;
			colLen = 0;
			// capture column data type
			colType = odbColDataType( hQry, i );
			// retrieve the column name
			sprintf( colName, "%s", (odbPSTR)odbColName( hQry, i ) );
			// try to retrieve the column
            if( pData = odbColData( hQry, usCol ) ) 
			{
				// convert based on data type (Binary data is not supported)
				switch( colType ) 
				{
					case ODB_BIT: colData_n = (	(odbULONG)*((odbPBYTE)pData) ); break;
					case ODB_TINYINT: colData_n = (	(odbLONG)*((odbPBYTE)pData) ); break;
					case ODB_UTINYINT: colData_n = ( (odbULONG)*((odbPBYTE)pData) ); break;
					case ODB_SMALLINT: colData_n = ( (odbLONG)*((odbPSHORT)pData) ); break;
					case ODB_USMALLINT: colData_n = ( (odbULONG)*((odbPUSHORT)pData) ); break;
					case ODB_INT: colData_n = (	*((odbPLONG)pData) ); break;
					case ODB_UINT: colData_n = ( *((odbPULONG)pData) ); break;
					case ODB_BIGINT: colData_n = ( odbLongLongToStr( *((odbPLONGLONG)pData), &sz[31] ) ); break;
					case ODB_UBIGINT: colData_n = (	odbULongLongToStr( *((odbPULONGLONG)pData), &sz[31] ) ); break;
					case ODB_REAL: colData_n = ( *((odbPFLOAT)pData) ); break;
					case ODB_DOUBLE: colData_n = ( *((odbPDOUBLE)pData) ); break;
					case ODB_DATETIME:
						// retrieve dt structure
						pts = (odbPTIMESTAMP)pData;
						// set text data directly
						sprintf( colData_s, "%s%04i-%02i-%02i %02i:%02i:%02i.%i", pts->sYear, pts->usMonth, pts->usDay, pts->usHour, pts->usMinute, pts->usSecond, pts->ulFraction );
						// set data length
						colLen = odbColDataLen( hQry, i );
						break;
					case ODB_CHAR:
					case ODB_WCHAR:
						// set text data directly
						sprintf( colData_s, "%s", (odbPSTR)odbColData( hQry, i ) );
						// set data length
						colLen = odbColDataLen( hQry, i );
						break;
				} // end switch
            } // end if
			else {colData_s = "NULL"; colLen = 4;} // null value detected

        } // end for
	} // end while
}

Open in new window

typedef struct oColumns
{
	short int	column; // column count property
	short int	type; // odbtp type
	char		*name; // column name
	union		data // column data
	{
		char	*c; // data of text type formats
		double	i; // data of numeric formats
		float	f; // data of float or money formats
	}
	double		length; // the column length (applies to text)
} oColumns; // base of a table for odbtp values
typedef struct oMetadata
{
	bool		isError; // if an error occurred
	size_t		rowcount; // number of rows returned
	size_t		colcount; // number of columns in each row
	char		*message; // error message
} oMetadata; // metadata about the odbtp execute

Open in new window

0
Comment
Question by:dilithiumtoys_dot_com
  • 6
  • 3
9 Comments
 
LVL 13

Expert Comment

by:Superdave
ID: 34923151
Line 41 in the first section should probably be:
*table = malloc(rows * sizeof(oColumns));

and it looks to me like oColumns would be better named oField.  The * before table allows the caller of oExecute to get the value.  It has to call it passing a pointer to the row for the second argument.  But you're not really setting anything, just allocating memory, so maybe there's no need to do that malloc in oExecute.  After you call oExecute to get the dimensions, you could just malloc one big array:

table = malloc(rows * columns * sizeof oColumns);

In the second section (oReader) you are setting local variables; maybe that's just for testing and debugging but of course you ultimately need to change that to set the elements of your oColumns structure.

There is a lot more confusion about memory issues in that code, such as declaring pointers where you need buffers.  In line 26 and 31 in the first section, you cannot use errors as an argument to sprintf because it has not been assigned.  You would have to assign errors to a static or global character array.  In the second part, colName should be a character array, or else you'd need to malloc memory for it.  If you know the maximum length of your field names, it would be easier just to declare a character buffer instead of a pointer in your oColumns structure.

One other minor nitpick, in the third section:  I've never seen a fractional-length string of text, so that could probably be size_t instead of double.


0
 

Author Comment

by:dilithiumtoys_dot_com
ID: 34927650
Thanks Superdave! First off I really appreciate your thoughs, but I have a few questions though.

Quick Notes:
1) The line in 41 was just before I ask the question because I was trying to figure out how I would modify for my situation.
2) I took your suggestion and changed oColumns to oFields for better clarity.
3) I am using local variables in section 2 (oReader) because I want to be sure that all the data is present before writing to the column or row.
4) I changed colName to colName[100] because the field names in the database are not more than that.

My questions:

1) Say I have a calling function of getYearEndSales. How SHOULD I declare a variable of OField type to pass to oExecute?

2) You will answer this one by telling me the answer to the first question. May I pass a null pointer to oReader and malloc in oReader?

3) should the table variable be referenced as "*table->" or "(*table)->"

4) I gleaned the the attached code when seeing if what I wanted to do was possible.You mentioned to execute this "table = malloc(rows * columns * sizeof oColumns);" Which should I do if I want an actual table in memory?

5) I am confused about what you mean by the errors variable is not assigned and why I would need to use a global or static character array. Please clarify. My intent to use errors is not so that it is visible to any other function then the local one. I push errors into the oMetadata metadata structure that I passed to oExecute. Also I do not know the size of the potential error so I am unable to predefine a buffer size.

Thanks for your help

int **array;
	array = malloc(nrows * sizeof(int *));
	if(array == NULL)
		{
		fprintf(stderr, "out of memory\n");
		exit or return
		}
	for(i = 0; i < nrows; i++)
		{
		array[i] = malloc(ncolumns * sizeof(int));
		if(array[i] == NULL)
			{
			fprintf(stderr, "out of memory\n");
			exit or return
			}
		}

Open in new window

0
 

Author Comment

by:dilithiumtoys_dot_com
ID: 34927737
Ok, understand what you are saying about the char* pointers now. I will have to use malloc to create them.
0
 
LVL 13

Expert Comment

by:Superdave
ID: 34927978
I'll look at this tonight when I'm off work, but meanwhile post what you've changed with malloc'ing pointers and I'll look at that too.
0
Do You Know the 4 Main Threat Actor Types?

Do you know the main threat actor types? Most attackers fall into one of four categories, each with their own favored tactics, techniques, and procedures.

 

Author Comment

by:dilithiumtoys_dot_com
ID: 34930993
I have posted modified code. I change somethings around.

I don't want to be presumptuous about if you have ever built Apache modules, but please note that I am using this function in an Apache server environment, so you will see functions that are the Apache implementations of the C native function.

I used apr_pstrdup and apr_psprintf for my strings because the function allocates and initializes the strings at the same time.

I did away with oReader because it was not necessary to break up the code.

I am having difficulty with the items that are in between the lines of asterisks. Minus this code, this project compiles and works as expected.

I have determined that I can't obtain the number of rows before hand. So my thoughts were to initialize for one row and the number of columns found. Then use realloc to increase the number of rows in the array.

I can clearly state that I have no idea on what I am doing when it comes to malloc and building the array of arrays.

Please help me!!!
typedef struct oFields
{
	size_t		column; // column count property
	size_t		type; // odbtp type
	char		*name; // column name
	char		*data; // column data (already as string)
	size_t		length; // the column length (applies to text)
} oFields; // base of a table for odbtp values
typedef struct oMetadata
{
	size_t		isError; // if an error occurred
	size_t		rowcount; // number of rows returned
	size_t		colcount; // number of columns in each row
	char		*message; // error message
} oMetadata; // metadata about the odbtp execute

static void print (request_rec * r)
{
	char *sqlstring;
	oFields **table;
	oMetadata *metadata = (oMetadata *)apr_pcalloc(r->pool, sizeof (*metadata));
	
	sqlstring = apr_pstrdup(r->pool, "Select * from dbo.test");

	oExecute(r, sqlstring, *table, metadata);

	ap_rprintf(r, "%i\n%i\n%i\n%s\n",metadata->isError,metadata->rowcount,metadata->colcount,metadata->message);

	return;	
}
static void oExecute (request_rec * r, const char* sql, oFields **table, oMetadata *metadata)
{
	/*
		ODBTP sql execution function
		returns void
		accepts sql as string
		accepts pointer var table of oColumns struct type
		accepts pointer var metadata of OMetadata struct type 
	*/
	// variables
	odbHANDLE hCon, hQry;
	// get the config data into//ap_rprintf(r, "MESSAGE: %s\n", odbGetResponse( hQry ));
	//--> variables to be put into our metadata collection
	char *errors = apr_pstrdup(r->pool, ""); // any errors that occur go here
	size_t	col = 0, rows = 1, cols = 0;	// row/col count and counter
	int		colType, colLen = 0, isError = 0;
	char	*colName = apr_pstrdup(r->pool, "");
	char	*colData_s = apr_pstrdup(r->pool, "");
	// sanity checks
	if (!strlen(sql)){errors = apr_pstrdup(r->pool, "SQL String must not be zero length!"); goto complete; } // sql string must be valid
	// initalize odbtp network library
	if ( !odbWinsockStartup() ){ errors = apr_pstrdup(r->pool, "Could not initiate network transfer."); goto complete; }
	// check that we can connect
	if( !(hCon = odbAllocate(NULL) ) ) { errors = apr_pstrdup(r->pool, "Could not allocate connection"); goto complete; }
    if( !odbLogin(hCon, SQL_SERVER, 2799, ODB_LOGIN_NORMAL, SQL_STRING )) { errors = apr_pstrdup(r->pool, "Could not login"); goto complete; }
    if( !(hQry = odbAllocate( hCon ) ) ) { errors = apr_pstrdup(r->pool, "Could not allocate query"); goto complete; }
	if( !odbExecute( hQry, sql ) ){  errors = apr_psprintf(r->pool, "Could not execute query: %s", odbGetErrorText( hQry )); goto complete; }
	// check for data --> no data does not imply an error
	if ( odbNoData( hQry ) )
	{ 
		// copy error buffers
		errors = apr_psprintf(r->pool, "%s", odbGetErrorText( hQry ));
		// check if there were any errors
		if (strlen(errors) && errors != "(null)")
			isError = 1;
		// exit
		goto complete; 
	}
	// retrieve column count
	cols = odbGetTotalCols( hQry ); // column count
	// read from stream
	if (cols)
	{

*****************************************************
		// intialize the table for one row
		(*table) = apr_pcalloc(r->pool, rows * sizeof (oFields));
		if (*table == NULL){ errors = apr_pstrdup(r->pool,"Could not create first row!"); goto complete; }
		// intialize the table for the number of columns
		*table[0] = apr_pcalloc(r->pool, cols * sizeof (oFields));
		if (*table[0] == NULL){ errors = apr_pstrdup(r->pool,"Could not create columns!"); goto complete; }

*****************************************************

		// loop over rows to get data
		while( odbFetchRow( hQry ) && !odbNoData( hQry ) ) 
		{
			// loop over columns
			for (col = 1; col <= cols; col++)
			{
				// reset variables
				colData_s = apr_pstrdup(r->pool, "");
				colLen = 0;
				// retrieve the column name
				colName = apr_pstrdup(r->pool,(odbPSTR)odbColName( hQry, col ) );
				// retrieve column data type
				colType = odbColDataType( hQry, col );
				// try to retrieve the column
				if( odbColData( hQry, col ) ) 
				{
					// convert based on data type
					if (colType == ODB_CHAR || colType == ODB_WCHAR)
					{
						// set text data directly
						colData_s = apr_psprintf(r->pool, "%s", (odbPSTR)odbColData( hQry, col ) );
						// set data length
						colLen = odbColDataLen( hQry, col );
					}
					else if (colType == ODB_DATETIME)
					{
						// set text data directly
						odbTimestampToStr(colData_s,(odbPTIMESTAMP) odbColData( hQry, col ), 0);
						// set data length
						colLen = strlen(colData_s);
					}
					else
					{ 
						// set text data directly
						colData_s = apr_psprintf(r->pool, "%i", odbColData( hQry, col )); 
						// set data length
						colLen = strlen(colData_s);					
					}
				} // end if
				else {colData_s = apr_pstrdup(r->pool, "NULL"); colLen = 4;} // null value detected
			} // end for
			// increment row count
			rows++;
		} // end while
	}
	// logout
	if( !odbLogout( hCon, FALSE ) ) { errors = apr_pstrdup(r->pool,"Could not logout"); goto complete; }
	// end
	goto complete;
	//---> Exit Procedure
	complete:
		// determine if the isError flag is set or there is any data in errors
		if (strlen(errors) || isError)
			isError = 1;
		else
			errors = apr_pstrdup(r->pool,"No Errors");
		/* set metadata members */
		metadata->isError = isError; // set bool isError flag
		metadata->rowcount = rows; // set row count
		metadata->colcount = cols; // set column count
		metadata->message = errors; // set errors
		/* clean up */
		odbFree( hQry ); // free odbtp query
		odbFree( hCon ); // free odbtp connection
		odbWinsockCleanup(); // clean up network connection
		/* return */
		return;
}

Open in new window

0
 

Author Comment

by:dilithiumtoys_dot_com
ID: 34931377
I changed parts of the oExecute and I was able to make it compile, but it is not working as expected. I have attached the code.

I can get the desire result out of the table within the function, but not in the caller. Please advise as to what I should do.

Thanks
static void oExecute (request_rec * r, const char* sql, oFields **table, oMetadata *metadata)
{
	/*
		ODBTP sql execution function
		returns void
		accepts sql as string
		accepts pointer var table of oColumns struct type
		accepts pointer var metadata of OMetadata struct type 
	*/
	// variables
	odbHANDLE hCon, hQry;
	//--> variables to be put into our metadata collection
	char *errors = apr_pstrdup(r->pool, ""); // any errors that occur go here
	size_t	col = 0, row = 0, rows = 0, cols = 0;	// row/col count and counter
	int		colType, colLen = 0, isError = 0;
	char	*colName = apr_pstrdup(r->pool, "");
	char	*colData = apr_pstrdup(r->pool, "");
	// sanity checks
	if (!strlen(sql)){errors = apr_pstrdup(r->pool, "SQL String must not be zero length!"); goto complete; } // sql string must be valid
	// initalize odbtp network library
	if ( !odbWinsockStartup() ){ errors = apr_pstrdup(r->pool, "Could not initiate network transfer."); goto complete; }
	// check that we can connect
	if( !(hCon = odbAllocate(NULL) ) ) { errors = apr_pstrdup(r->pool, "Could not allocate connection"); goto complete; }
    if( !odbLogin(hCon, SQL_SERVER, 2799, ODB_LOGIN_NORMAL, SQL_STRING )) { errors = apr_pstrdup(r->pool, "Could not login"); goto complete; }
    if( !(hQry = odbAllocate( hCon ) ) ) { errors = apr_pstrdup(r->pool, "Could not allocate query"); goto complete; }
	if( !odbExecute( hQry, sql ) ){  errors = apr_psprintf(r->pool, "Could not execute query: %s", odbGetErrorText( hQry )); goto complete; }
	// check for data --> no data does not imply an error
	if ( odbNoData( hQry ) )
	{ 
		// copy error buffers
		errors = apr_psprintf(r->pool, "%s", odbGetErrorText( hQry ));
		// check if there were any errors
		if (strlen(errors) && errors != "(null)")
			isError = 1;
		// exit
		goto complete; 
	}
	// retrieve column count
	cols = odbGetTotalCols( hQry ); // column count
	// read from stream
	if (cols)
	{
		// intialize the table for one row
		table = apr_pcalloc(r->pool, sizeof (oFields *));
		if (table == NULL){ errors = apr_pstrdup(r->pool,"Could not create first row!"); goto complete; }
		// intialize the table for the number of columns
		table[0] = apr_pcalloc(r->pool, cols * sizeof (oFields));
		if (table[0] == NULL){ errors = apr_pstrdup(r->pool,"Could not create columns!"); goto complete; }
		// loop over rows to get data
		while( odbFetchRow( hQry ) && !odbNoData( hQry ) ) 
		{
			// loop over columns
			for (col = 1; col <= cols; col++)
			{
				// retrieve the column name
				colName = apr_pstrdup(r->pool,(odbPSTR)odbColName( hQry, col ) );
				// retrieve column data type
				colType = odbColDataType( hQry, col );
				// try to retrieve the column
				if( odbColData( hQry, col ) ) 
				{
					// convert based on data type
					if (colType == ODB_CHAR || colType == ODB_WCHAR)
					{
						// set text data directly
						colData = apr_psprintf(r->pool, "%s", (odbPSTR)odbColData( hQry, col ) );
						// set data length
						colLen = odbColDataLen( hQry, col );
					}
					else if (colType == ODB_DATETIME)
					{
						// set text data directly
						odbTimestampToStr(colData,(odbPTIMESTAMP) odbColData( hQry, col ), 0);
						// set data length
						colLen = strlen(colData);
					}
					else if (colType == ODB_INT)
					{ 
						// set text data directly
						colData = apr_psprintf(r->pool, "%i", odbColData( hQry, col )); 
						// set data length
						colLen = strlen(colData);					
					}
				} // end if
				else {colData = apr_pstrdup(r->pool, "NULL"); colLen = 4;} // null value detected
				
				// insert into table
				table[rows][col - 1].column = col;
				table[rows][col - 1].type = colType;
				table[rows][col - 1].name = apr_psprintf(r->pool, "%s", colName);
				table[rows][col - 1].data = apr_psprintf(r->pool, "%s", colData);
				table[rows][col - 1].length = colLen;
			} // end for
			// increment row count
			rows++;
		} // end while
	}
	// logout
	if( !odbLogout( hCon, FALSE ) ) { errors = apr_pstrdup(r->pool,"Could not logout"); goto complete; }
	// end
	goto complete;
	//---> Exit Procedure
	complete:
		// determine if the isError flag is set or there is any data in errors
		if (strlen(errors) || isError)
			isError = 1;
		else
			errors = apr_pstrdup(r->pool,"No Errors");
		for (row = 0; row < rows; row++)
		{
			ap_rprintf(r,"row %d:\n",row);

			for (col = 0; col < cols; col++)
			{
				ap_rprintf(r,"%d	",col);
				ap_rprintf(r,"%d	",table[row][col].column);
				ap_rprintf(r,"%d	",table[row][col].type);
				ap_rprintf(r,"%s	",table[row][col].name);
				ap_rprintf(r,"%s	",table[row][col].data);
				ap_rprintf(r,"%d",table[row][col].length);
				ap_rputs("\n", r);
			}
		} ap_rprintf(r,"\n%d",table[0][0].column);
		ap_rprintf(r,"%d\n",table);
		/* set metadata members */
		metadata->isError = isError; // set bool isError flag
		metadata->rowcount = rows; // set row count
		metadata->colcount = cols; // set column count
		metadata->message = errors; // set errors
		/* clean up */
		odbFree( hQry ); // free odbtp query
		odbFree( hCon ); // free odbtp connection
		odbWinsockCleanup(); // clean up network connection
		/* return */
		return;
}

Open in new window

0
 

Author Comment

by:dilithiumtoys_dot_com
ID: 34931385
Here are the results of the program

row 0:
0      1      1      ret      OK Receipt:       12
1      2      -16      x      14026088      8
2      3      -16      y      14026232      8
3      4      -16      z      14026376      8
4      5      -16      c      14026520      8
5      6      93      a      2011-02-18 20:47:35      19

address of table from inside oExecute: 114021904
address of table from caller: 9635400

I am noticing that there is two different addresses for the table
0
 
LVL 13

Accepted Solution

by:
Superdave earned 500 total points
ID: 34931632
That looks pretty close to working.  The problem with table address is that oExecute ignores whatever value you're passing in table and just reassigns table to the address allocated by apr_pcalloc.  One way to deal with that is to change the parameter to "oField ***tablep", then declare table a local variable, then at the very end of the procedure, say *tablep = table to assign the result.  You would then call oExecute passing it &table for that parameter.  A simpler way would be to declare oExecute **oFields instead of void, have it "return table;" at the end, then call it by "table = oExecute(r,sql,metadata)".

The other thing with malloc and realloc would be to resize table each time through the row loop.  I Googled apr but didn't see a realloc skimming through it, so I'll show how to use real malloc and realloc.  The stuff at line 44 needs to be moved into the while loop to allocate memory for each row.  At line 44 initialize table to NULL ptr, which will make realloc work right the first time:

      table = 0;

(I think that is perfectly okay according to the C standard, but some people will prefer
"table = NULL" or "table = (oFields **)0".)

Then inside the while loop at line 52, move the rest of the lines with table changed to use realloc:

table = realloc(table,(rows+1) * sizeof (oFields *));
if (table == NULL){ errors = apr_pstrdup(r->pool,"Could not create first row!"); goto complete; }
// intialize the table for the number of columns
table[rows] = apr_pcalloc(r->pool, cols * sizeof (oFields));
if (table[rows] == NULL){ errors = apr_pstrdup(r->pool,"Could not create columns!"); goto complete; }


If you're sure there won't be more than a certain number of rows and especially if this is for a quick-and-dirty one time thing, you could just set table to an allocation of say 100000 pointers and forget about the realloc business.
      
0
 

Author Closing Comment

by:dilithiumtoys_dot_com
ID: 34933061
Superdave, you rock!!! Thanks for all your help.
0

Featured Post

What Is Threat Intelligence?

Threat intelligence is often discussed, but rarely understood. Starting with a precise definition, along with clear business goals, is essential.

Join & Write a Comment

This tutorial is posted by Aaron Wojnowski, administrator at SDKExpert.net.  To view more iPhone tutorials, visit www.sdkexpert.net. This is a very simple tutorial on finding the user's current location easily. In this tutorial, you will learn ho…
Summary: This tutorial covers some basics of pointer, pointer arithmetic and function pointer. What is a pointer: A pointer is a variable which holds an address. This address might be address of another variable/address of devices/address of fu…
The goal of this video is to provide viewers with basic examples to understand recursion in the C programming language.
The goal of this video is to provide viewers with basic examples to understand and use conditional statements in the C programming language.

758 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

21 Experts available now in Live!

Get 1:1 Help Now