mem leak while storing pointers in vector<vector< void *>>

This is related to this thread: http://www.cplusplus.com/forum/general/36365/

I'm wondering if my code causes a memory leak. From what I've read, each use of "new" should have a "delete" and "new[]" should have a "delete[]".
When I include a "delete", my data object ends up being stored in the same address, overwriting anything that was there before.

Object: return a two dimensional vector of void pointers to the calling method. Must be 2d-vector of void pointers because the Query method itself does not know what or how much data will be retrieved. It only knows it will retrieve data in a tabular format.

What I currently have, copied below, retrieves the data as expected. However, it does not have a "delete" call. When I uncomment the "delete" line, the Query method reuses the first memory address for CT, causing my 2D vector to contain data only for the last retrieved row of data.

Declaration of Query method:

1
2
3
4
5
class DatabaseODBC : public Database
{
...
	int Query(std::vector< std::vector<void*> > &, char*, bool SavedQuery = true);
}


Calling code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
	DatabaseODBC db;
	// Connect to DB
	if (db.Connect() == 1)
	{
		std::vector< std::vector<void*> > ResultTable;
		db.Query(ResultTable, "SELECT ID,Name FROM Race", false);
		int x;
		char* y;
		for (unsigned int Row = 0; Row < ResultTable[0].size(); Row++)
		{
			x = *((int*)(ResultTable[0][Row]));
			y = (char*)ResultTable[1][Row];
			std::cout<<x<<" "<<y<<std::endl;
		}
	}


Definition of Query method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
int DatabaseODBC::Query(std::vector< std::vector<void*> > &ResultTable, char* QueryName, bool SavedQuery)
{
	// Check that the database connection is established
	if (SQLHandleDatabaseConnection == SQL_NULL_HDBC)
		return -1;

	SqlReturnCode = SQLAllocHandle(SQL_HANDLE_STMT, SQLHandleDatabaseConnection, &SQLHandleStatement);
	if(SqlReturnCode != SQL_SUCCESS && SqlReturnCode != SQL_SUCCESS_WITH_INFO)
		return -2;

	char QueryToExecute[8000];
	if (SavedQuery)
	{
		// Retrieve the actual query from the table of queries
		char GetQuery[8000];
		strcpy_s(GetQuery, "SELECT QueryText FROM QueryList WHERE QueryName LIKE '");
		strcat_s(GetQuery, QueryName);
		strcat_s(GetQuery, "'");
		SqlReturnCode = SQLExecDirect(SQLHandleStatement, (SQLCHAR*)GetQuery, SQL_NTS);
		if(SqlReturnCode != SQL_SUCCESS && SqlReturnCode != SQL_SUCCESS_WITH_INFO)
			return -3;

		strcpy_s(QueryToExecute,"");
		while (SQLFetch(SQLHandleStatement) == SQL_SUCCESS)
		{
			SqlReturnCode=SQLGetData(
									SQLHandleStatement,				//SQLHSTMT StatementHandle,
									1,								//SQLUSMALLINT ColumnNumber,
									SQL_C_CHAR,						//SQLSMALLINT TargetType,
									_DBRow_QueryList.Text,			//SQLPOINTER TargetValuePtr,
									sizeof(_DBRow_QueryList.Text),	//SQLINTEGER BufferLength,
									&_DBRow_QueryList.TextLength);	//SQLINTEGER * StrLen_or_IndPtr
			strcat_s(QueryToExecute, (char*)_DBRow_QueryList.Text);
		}

		//free the statement handle so that it can be reused
		SQLFreeHandle(SQL_HANDLE_STMT, SQLHandleStatement);
	}
	else
		strcpy_s(QueryToExecute, QueryName);
	//allocate the statement handle again
	SqlReturnCode = SQLAllocHandle(SQL_HANDLE_STMT, SQLHandleDatabaseConnection, &SQLHandleStatement);
	if(SqlReturnCode != SQL_SUCCESS && SqlReturnCode != SQL_SUCCESS_WITH_INFO)
		return -2;

	//execute the query string retrieved from the QueryList table
	SqlReturnCode = SQLExecDirect(SQLHandleStatement, (SQLCHAR*)QueryToExecute, SQL_NTS);
	if(SqlReturnCode != SQL_SUCCESS && SqlReturnCode != SQL_SUCCESS_WITH_INFO)
		return -3;

	SQLSMALLINT NumColumns;
	SqlReturnCode = SQLNumResultCols(SQLHandleStatement, &NumColumns);
	if(SqlReturnCode != SQL_SUCCESS && SqlReturnCode != SQL_SUCCESS_WITH_INFO)
		return -4;

	//Get specifications for each column in order to fetch the data
	std::vector<ColumnInfo> ColInfo;
	ColInfo.resize(NumColumns);
	for (SQLSMALLINT i=0; i<NumColumns; i++)
	{
			SQLDescribeCol (
					SQLHandleStatement,
					i+1,
					ColInfo[i].ColumnName,
					sizeof (ColInfo[i].ColumnName),
					&ColInfo[i].ColumnNameLength,
					&ColInfo[i].ColumnType,
					&ColInfo[i].ColumnSize,
					&ColInfo[i].DecimalDigits,
					&ColInfo[i].Nullable);
			//convert to SQL_CHAR if necessary so SqlGetData knows how to process
			switch (ColInfo[i].ColumnType)
			{
			case SQL_VARCHAR : ColInfo[i].ColumnType = SQL_CHAR; break;
			default : break;
			}
	}
	//ResultTable accesses its data via ResultTable[Column][Row]
	//This requires fewer resizes, since the number of columns is predetermined
	ResultTable.resize(NumColumns);

	ColumnType* CT;
	int IndexRow	= 0;
	while (SQLFetch(SQLHandleStatement) == SQL_SUCCESS)
	{
		for (SQLSMALLINT IndexColumn=0; IndexColumn < NumColumns; IndexColumn++)
		{
			CT = new ColumnType; // if CT is not deleted, possible memory leak!
			switch(ColInfo[IndexColumn].ColumnType)
			{
//			case SQL_UNKNOWN_TYPE :		//0
			case SQL_CHAR :				//1
				{
					SqlReturnCode=SQLGetData(
						SQLHandleStatement,						//SQLHSTMT StatementHandle,
						IndexColumn+1,							//SQLUSMALLINT ColumnNumber, starting at 1
						ColInfo[IndexColumn].ColumnType,		//SQLSMALLINT TargetType,
						&CT->_SqlChar,						//SQLPOINTER TargetValuePtr,
						sizeof(CT->_SqlChar),			//SQLINTEGER BufferLength,
						&CT->_ObjectLength);			//SQLINTEGER * StrLen_or_IndPtr
					ResultTable[IndexColumn].push_back((void*)CT->_SqlChar);
					//To dereference this value, use the following as a template
					//					char* x = (char*)ResultTable[IndexColumn][IndexRow];

				} break;
			case SQL_NUMERIC :			//2
				{
				} break;
			case SQL_DECIMAL :			//3
				{
				} break;
			case SQL_INTEGER :			//4
				{
					SqlReturnCode=SQLGetData(
						SQLHandleStatement,						//SQLHSTMT StatementHandle,
						IndexColumn+1,							//SQLUSMALLINT ColumnNumber, starting at 1
						ColInfo[IndexColumn].ColumnType,		//SQLSMALLINT TargetType,
						&CT->_SqlInteger,				//SQLPOINTER TargetValuePtr,
						sizeof(CT->_SqlInteger),		//SQLINTEGER BufferLength,
						&CT->_ObjectLength);			//SQLINTEGER * StrLen_or_IndPtr

					ResultTable[IndexColumn].push_back(&CT->_SqlInteger);
					//To dereference this value, use the following as a template
					//					int x = *((int*)(ResultTable[IndexColumn][IndexRow]));
				} break;
			case SQL_SMALLINT :			//5
				{
				} break;
			case SQL_FLOAT :			//6
				{
				} break;
			case SQL_REAL :				//7
				{
				} break;
			case SQL_DOUBLE :			//8
				{
				} break;
			case SQL_DATETIME :			//9
				{
				} break;
			case SQL_VARCHAR :			//12
				{
				} break;
			case SQL_TYPE_DATE :		//91
				{
				} break;
			case SQL_TYPE_TIME :		//92
				{
				} break;
			case SQL_TYPE_TIMESTAMP :	//93
				{
				} break;
			default :
				{
				}
			}
// Does leaving this commented cause a memory leak?
// If so, how do I delete CT while not reusing a memory address for the 2D vector of pointers?
//			delete CT;
		}
		IndexRow++;
	}

	//Release the query's handle from memory
	if (SQLHandleStatement != SQL_NULL_HSTMT)
		SQLFreeHandle(SQL_HANDLE_STMT, SQLHandleStatement);
	
	return 0;
}

Any ideas? The best answer, but most unlikely I expect, is that this code doesn't cause memory leaks. Anybody able to give me some tips?

Last edited on
closed account (zb0S216C)
A void pointer( void* )can point to any object. Using this in a vector can be dangerous for your memory( Not physically dangerous ). When it comes to deleting the elements of the vector, the vector cannot tell the size of each element. This means that the vector cannot delete all of the memory allocated by each element.

Are you deleting the elements manually or are you letting the vector do that?

I'm probably way off, but that's my take.
Again, I rushed my post and forgot something. At the top of my post is a link to another thread, but it would be clearer if I posted here.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static struct ColumnType
{
public:
// Fix Later				//SQL_UNKNOWN_TYPE		//0
SQLCHAR		_SqlChar[8000];	//SQL_CHAR				//1
// Fix Later				//SQL_NUMERIC			//2
// Fix Later				//SQL_DECIMAL			//3
SQLINTEGER	 _SqlInteger;	//SQL_INTEGER			//4
// Fix Later				//SQL_SMALLINT			//5
// Fix Later				//SQL_FLOAT				//6
// Fix Later				//SQL_REAL				//7
// Fix Later				//SQL_DOUBLE			//8
// Fix Later				//SQL_DATETIME			//9
// Fix Later				//SQL_VARCHAR			//12
// Fix Later				//SQL_TYPE_DATE			//91
// Fix Later				//SQL_TYPE_TIME			//92
// Fix Later				//SQL_TYPE_TIMESTAMP	//93
SQLINTEGER	_ObjectLength;
}; _ColumnType;


The //Fix Later lines can be ignored for now. I'm currently only testing SQLINTEGER and SQLCHAR values.

What's going on in this Query method is this:

1) Pass in a SQL SELECT statement
2) Execute the SELECT statement
3) Return the results

Since the Query method has no idea how many columns or what type of data those columns are going to be, the best approach I could think of short of hardcoding every single query result I'm ever going to make, was to store the retrieved data in a vector< vector< void* > >

It is the job of the calling method to manipulate/delete anything from the vector.

Next, the SqlGetData method requires specific data types passed into it in order to get the actual data. Because of this, I can't just pass in a cell from my vector. So there is a SqlGetData call for each target type of SQL data (SQLCHAR, SQLINTEGER, ...) I used the static struct ColumnType {...} _ColumnType; to hold each type, although I could simply have created a SQLCHAR just for when a SQLCHAR was needed and SQLINTEGER just when SQLINTEGER was needed.

Anyways, I need to save the data into *something* that I can then dump into my vector of void pointers.

The problem I'm having right now, is that I can only do this by adding more ColumnType objects to the heap. If I delete each instance of ColumnType, when I'm done saving, the next time through my loop uses the same memory address for new data.

As I'm typing this, I think I can create a vector/array of SQLINTEGER to hold the values while I save each address into the vector of void pointers, and repeat the process for SQLCHAR, etc. I'm going to give this a whirl and tell my findings.
That failed. I got unique addresses, but apparently the vector destructor released them, so accessing those addresses later (since they were saved in the void* vector) just returned junk.

At the moment, the only way I've been able to access all of the data from the calling method has been to load up the heap with "new ColumnType" without deleting them. From everything I've read, that's just a disaster waiting to happen.
Topic archived. No new replies allowed.