Is this C code using pointers valid?

Started by
11 comments, last by FRex 9 years ago

Hi all.

I'm trying to write a C function that returns a dynamic list of servers. My pointer memory is a bit rusty so I just wanted to check if the code below is correct. Although the output is as expected, i.e. 3 servers with IP & name, and there appears to be no memory leaks (used Visual Leak Detector) I just want to triple check. Note, I'm just interested in knowing if the pointer usage is correct rather than actual logic or error checking. I'm using VS 2013 express. Thanks in advance.


#include "stdafx.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h> 

struct serverInfo
{
	char serverName[50];
	char serverIP[20];
};

int getServerListTest(serverInfo** pServers);

int _tmain(int argc, _TCHAR* argv[])
{
	serverInfo *serverList = (serverInfo*)calloc(1, sizeof(serverInfo));
	serverInfo **serverListPtr = &serverList;
	int serverCount = getServerListTest(serverListPtr);
	
	for (int i = 0; i < serverCount; i++)
	{
		printf("ip:%s name:%s\n", (*serverListPtr+i)->serverIP, (*serverListPtr+i)->serverName);
	}
	
	free(serverList);
	serverListPtr = NULL;

	getchar();
	return 0;
}

int getServerListTest(serverInfo** pServers)
{
	serverInfo* current = NULL;
	int serverCount= 0;

	char serverNo[5];
	for (int i = 0; i < 3; i++)
	{
		if (serverCount > 0)
		{
			current = (serverInfo*)realloc((*pServers), sizeof(serverInfo) * (serverCount+1));
			if (current)
			{
				pServers = &current;
			}
			else
			{
				return -1;
			}
		}
		_itoa(serverCount+1, serverNo, 10);
		strcpy((*pServers + serverCount)->serverIP, "10.10.100.10");
		strcat((*pServers + serverCount)->serverIP, serverNo);
		strcpy((*pServers + serverCount)->serverName, "Server");
		strcat((*pServers + serverCount)->serverName, serverNo);
		serverCount++;
	}
	
	return serverCount;
}
Advertisement

It's very wrong, there is potential for leak, double free, reading from freed memory and maybe more, because what realloc returns is never written through pServer (just to it's local copy, this is a substantial difference).

There is much much more wrong with this code all around too and it looks as if it's obfuscated on purpose and it's not even proper C...

I voted your answer down FRex because without any concrete examples it is unnecessarily negative.

Saying that it's "obfuscated on purpose" and that "it's not even proper C" is net helpful. Unless you demonstrate how to do it 'properly'.

Just my thoughts.

I am not really an expert in C so I can't offer any advice on how sound/unsound the code is.

However, the code doesn't look awful at all. ;)

Too many projects; too much time

Consider using array notation whenever appropriate, it's often a good idea to distinguish pointers to objects from pointers as arrays in your code and as a bonus it helps reason about the different levels of indirection involved. In fact non-temporary arrays should probably eventually be wrapped into a struct and given proper access patterns and ownership if they are used regularly.

Just because it's written in C doesn't mean it has to be hemorrhaging asterisks and ampersands, giving higher-level structure to your code beyond what the syntax offers is part of what differentiates high quality, readable/maintainable code from procedural, spaghetti code with plenty of side effects smile.png

I voted your answer down FRex because without any concrete examples it is unnecessarily negative.

Saying that it's "obfuscated on purpose" and that "it's not even proper C" is net helpful. Unless you demonstrate how to do it 'properly'.

Just my thoughts.

I am not really an expert in C so I can't offer any advice on how sound/unsound the code is.

However, the code doesn't look awful at all. ;)

FRex may be a bit blunt, however he does raise some good points, the code has a serious flaw, although I think besides the main problem of the reallocated array not being returned outside the function (you want *pServers = current) the rest mostly has to do with structuring the code, architectural concerns, avoiding redundant variables and better variable scoping, maybe using snprintf to build the server name from the server index without going through itoa and a temporary buffer, ... although those aren't the pointer problems the OP requested comments on.

“If I understand the standard right it is legal and safe to do this but the resulting value could be anything.”

			current = (serverInfo*)realloc((*pServers), sizeof(serverInfo) * (serverCount+1));
			if (current)
			{
				pServers = &current;
			}
			else
			{
				return -1;
			}

This part is broken.... I think (the logic is confusing).
You reallocate the input data, so it seems that you want to pass that new allocation back out to the caller. If so, it should be:
(*pServers) = (serverInfo*)realloc((*pServers), sizeof(serverInfo) * (serverCount+1));
Anywhere where you're taking the address of a local variable should be cause for great inspection of the logic smile.png


			current = (serverInfo*)realloc((*pServers), sizeof(serverInfo) * (serverCount+1));
			if (current)
			{
				pServers = &current;
			}
			else
			{
				return -1;
			}
This part is broken.... I think (the logic is confusing).
You reallocate the input data, so it seems that you want to pass that new allocation back out to the caller. If so, it should be:

(*pServers) = (serverInfo*)realloc((*pServers), sizeof(serverInfo) * (serverCount+1));
Anywhere where you're taking the address of a local variable should be cause for great inspection of the logic smile.png

Careful with this though, if realloc fails and returns a nil pointer you've lost your original pointer and you're screwed! biggrin.png Unless the caller knows that it has to make a copy of the input pointer, which can get complicated fast with more complex situations.

“If I understand the standard right it is legal and safe to do this but the resulting value could be anything.”

I voted your answer down FRex because without any concrete examples it is unnecessarily negative.

Saying that it's "obfuscated on purpose" and that "it's not even proper C" is net helpful. Unless you demonstrate how to do it 'properly'.

Just my thoughts.

I am not really an expert in C so I can't offer any advice on how sound/unsound the code is.

However, the code doesn't look awful at all. ;)

I will not demonstrate how to do it properly, because this is not what is being asked here, the question asked if the pointer usage is okay in this sample, and the answer (which I gave already, which you proudly downvoted, while contributing nothing, saying this broken code is okay and admitting you don't even know C well enough to help) is: No, there is potential for many bugs because what realloc returns is never written through pServer pointer, just to its' local copy.

The OP also specified to ignore actual logic and error checking, so I did. But it didn't ask to ignore other issues, like the fact that it's written in a (possibly) very confusing way, or that it's not proper C, so I pointed these two out.

This code may read freed memory.

This code may double free.

This code may leak memory.

This code is not proper standard C - it must be compiled with a C++ compiler (or a C compiler that accepts non standard code), at which point you might as well use safer C++ alternatives instead of juggling pointers.

although those aren't the pointer problems the OP requested comments on.

These aren't the issues we were asked to ignore either, and it's only fair to assume someone who asks for help with pointers is not aware about much more intricate issues with this code like the fact that it won't compile with more strict C compilers (GCC and clang couldn't do it), or with the fact that it's very hard to read at a glance to even semi experienced programmers like myself.

Edit: And the question is even worded: "Is this C code using pointers valid?" So my answer is: no, this C code is not valid, it's not even C code, and its' usage of pointers is one of the few issues.

i'd suggest the following algo:

1. do a pass and get a count of servers

2. malloc the ram for the list

3. do a second pass to populate the list

4. return a pointer to the list

5. perhaps use a separate result parameter for sending back error results (empty list, out of ram, other errors)

6. main() frees the pointer when done.

7. document the list generating subroutine so its CLEAR it allocates memory that the coder must track and free manually.

8. use array notation where possible for clarity.

when doing dynamic memory allocations, you want to keep them as simple and infrequent as possible, with either minimum or maximum scope.

minimum scope for temporary stuff. never keep ram allocated or a file open any longer than necessary - it cuts down on potential problems.

maximum scope for "global" stuff so you are almost always assured that at any point in the code the pointer is valid, and its easy to remember where its not (like program startup and shutdown). this can cut down on or eliminate the need for runtime checks for valid pointers.

things like this can simplify life with pointers.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php


7. document the list generating subroutine so its CLEAR it allocates memory that the coder must track and free manually.

you might even want to call your list generating routine something like:

create_server_list(*list,*result)

and write an explicit matching free routine:

free_server_list(*list)

which would just call free(*list).

that way your API has explicit create() and free() routines, making it more clear that your are creating and destroying a data structure on the heap.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

Damn, 1 little asterisk breaks it. Thanks for pointing that out. Not sure how it worked as expected anyway!

I did try using array notation but couldn't get it to work, so guess I need to read up about that a bit more unless someone is kind enough to show a quick example.

I tagged it as C as I didn't want people suggesting the use of new/delete or vector class etc. I'm using Visual C++ compiler so I confess it may not be *pure C* but for the purposes of this post it served its purpose.

FRex, I'm not sure what you mean by the following points

This code may read freed memory. - I don't access the pointer again after the call to free

This code may double free. - Not sure how\where I'm doing this?

Thanks for the replies and suggestions :)

This topic is closed to new replies.

Advertisement