Mundane C tricks ...

Discuss programming topics for the various GPL'd game engine sources.
Post Reply
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Mundane C tricks ...

Post by Baker »

None of this is rocket science, just some elementary code and macros used lately that has made life easier.

With C, a lot of stuff is kind of obscure at times but once you discover something, you can have a lot of fun with it. String macros, for instance, I had a ton of trouble getting any of them to work once upon a time (until I found the option in Visual Studio that let's you view a file AFTER the preprocessor macros have been evaluated ... )

Code: Select all

const_entry_uint_t messagenames[] =
	{	KEYVALUE(WM_ACTIVATE), KEYVALUE(WM_KILLFOCUS), KEYVALUE(WM_CLOSE),
		KEYVALUE(WM_DESTROY), KEYVALUE(WM_CREATE), KEYVALUE(WM_SYSCHAR), 
		KEYVALUE(WM_SIZE), KEYVALUE(WM_MOVE), KEYVALUE(WM_PAINT), KEYVALUE(WM_GETMINMAXINFO) 
	};  COUNT_ARRAY (messagenames);
With these macros ...

Code: Select all

#define KEYVALUE(x) { #x , x }
#define COUNT_ARRAY(_array) const int _num ## _array = sizeof(_array) / sizeof(_array[0])
typedef struct
	{
		const char* string;
		unsigned int value;
	} const_entry_uint_t;
Evaluates to ...

Code: Select all

messagenames[] = {
	{ "WM_ACTIVATE", WM_ACTIVATE },
	{ "WM_KILLFOCUS", WM_KILLFOCUS },
...
}; const int _nummessages = sizeof(messagenames) / sizeof(messagenames[0]);
Another funny trick (Windows only as far as I know) ... allocating a debug console to run along side main OpenGL window.

Code: Select all

		AllocConsole ();
		freopen("CONIN$", "rt", stdin);
		freopen("CONOUT$", "wt", stdout);
		freopen("CONOUT$", "wt", stderr); 
		SetConsoleTitle("Debug Console");
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED);
And along with this ...

Code: Select all

void Logme (const char *fmt, ...)
{
	va_list		argptr;
	char		msg[16384];
	
	va_start	(argptr, fmt);
	vsnprintf	(msg, sizeof(msg), fmt, argptr);
	va_end		(argptr);

	StringLCat (msg, "\n"); // <--------- append \n just so life is less annoying

	fprintf (stderr, "Warning: "); /// <------------------------- fprintf to stderr
	fprintf (stderr, msg);
}
I can do Logme ("Something"); and have an entirely separate DOS-like window just showing the messages.

And third one I found satisfying ...

Code: Select all

	byte* Win32_Bytes_From_Resource (int* numBytesSize, int resourceNum, const char* text_type_field_in_rc)
	{
		HMODULE handle	= GetModuleHandle(NULL);
		HRSRC rc		= FindResource(handle, MAKEINTRESOURCE(resourceNum), TEXT(text_type_field_in_rc));
		HGLOBAL rcData	= LoadResource(handle, rc);
		int numBytes	= SizeofResource(handle, rc);
		byte* rawData	= LockResource(rcData);
		
		*numBytesSize = numBytes;

		if (numBytes == 0)
			return NULL;
		return rawData;
	}
... to load whatever data out of a Windows resource file. And combined with this it could be saved to disk.

Code: Select all

fbool File_Memory_To_File (const char* fileToWrite, byte* data, int numBytes)
{
	FILE*	fout	= fopen (fileToWrite, "wb");

	if (!fout)											return False;
	if ( fwrite (data, numBytes, 1, fout) != 1)			return False;

	fclose (fout);

	return True;
}
Again, nothing "space age" above, but something I've found satisfying is after you build up a small mountain of these kinds of conveniences, things start getting really fast and easy and it seems like small inconveniences are the ones that really add to the length of time of making stuff and when you eliminate some of those everything becomes much more fun.

Case in point: The above allocating a separate window for messages that print to standard error, the next time I get around to messing with the Quake engine more that will save major time because I won't have to worry about everything printing to the console any more.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
taniwha
Posts: 401
Joined: Thu Jan 14, 2010 7:11 am
Contact:

Re: Mundane C tricks ...

Post by taniwha »

Hmm, should I be embarrased? I've been programming in C for 22 years and I didn't think of that KEYVALUE trick. But then again, I don't normally need to create arrays quite like that. However, I do use the sizeof(array)/sizeof(array[0]) trick quite a bit, though I tend to prefer using null entries to terminate the list.

The windows specific stuff: meh.

fprintf (stderr, ...) rocks. I use that a lot in qfcc (well, wrapped in error/warning/etc functions :))

Some of my favorite tricks:

Code: Select all

#define field_offset(type,field) ((size_t)&(((type *)0)->field))
Of course, trying to read or write a struct field through a null pionter will get you a segfault, but it's perfectly valid to take the address of a field through a null pointer :). And yes, there is an offsetof macro, but at the time field_offset was written, offsetof wasn't very portable (ie, not all systems provided the macro).

Then combine the above with the following.

Code: Select all

typedef struct hashtab_s {
    ...:
    hashlink_t *tab[1];
} hashtab_t;

...
    hashtab_t *tab = calloc (1, field_offset (hashtab_t, tab[tsize]));
With the above, one can have variable sized data attached to a struct all in the one block of memory.

Another favorite trick is to use double pointers with linked lists. There are three methods I use: one for doubly linked lists, and two for singly linked lists.

First, doubly linked lists:

Code: Select all

typedef struct hashlink_s {
    struct hashlink_s *next;
    struct hashlink_s **prev;
    void *data;
} hashlink_t;
next is as one would expect, the magic comes with prev, and works best when items are always added to the list at the beginning of the list:

Code: Select all

if (list_head)
    list_head->prev = &item->next;
item->next = list_head;
item->prev = &list_head;
list_head = item;
The magic really comes into play when removing any item from the list:

Code: Select all

if (item->next)
  item->next->prev = item->prev;
*item->prev = item->next;
Removal is much simpler because there's no special case for deleting the first element of the list: item->prev points to the list_head pointer :).

Next, singly-linked lists, trick one: easy appending to the list:

Code: Select all

list_t *head = 0;
list_t **tail = &head;
list_t *item;
...
*tail = item;
tail = &item->next;
item->next = 0;
With care, tail can be used for accessing the last item on the list by casting tail to (list_t *) (ie, dropping a level of indirection). Tail is never null, but accessing an empty list will result in an invalid list item. I ran into this problem recently in qfcc :/. Just check head first as head will be null for an empty list.

And for singly-linked lists, trick two: easy deletion of any element (have to search, though):

Code: Select all

list_t **item = &head;
while (*item) {
    if (match(item)) {
        *item = (*item)->next;
    } else {
        item = &(*item)->next;
    }
}
The above allows one to scan through a list removing multiple items in the one scan.

Hmm, other than that, I can't think of any favorite C idioms.
Leave others their otherness.
http://quakeforge.net/
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Mundane C tricks ...

Post by Baker »

taniwha wrote:The windows specific stuff: meh.
I'm only after Windows specific stuff that makes my stuff non-Windows specific.
1. The fprintf stderr ... as an example and secondary console window. On Linux I'm rather sure that just "happens" in the console.
2. On a Mac, applications are essentially self-contained folders. Windows that can't be done, but if I want "built-in" graphics or data the resource file loading stuff is important to me. Especially since I can, for example, load images from memory (although this is no doubt possible with libpng, I ended up opting with the DevIL library as it is far more refined, and that has load from memory functions (even if I didn't use that, I could opt for TGA and load from memory easily, but that would be slightly foolish as a TGA isn't compressed well like png/jpg and that would bloat up .exe sizes).

[No I don't have the absurd idea of cramming 64MB of data into an .exe, but I want to embed some a few baseline graphics that are expected to exist and not have the annoyance of the possibility someone deleted them or did something boneheaded.]

re: links ... I haven't entirely come up with a linking strategy I like in all situations. :( I'm kind of close, but I consider my current "linking strategy" as a bit hackish as it expects the top X members of any given data structure to be fixed. Your code looks like it essentially "super-classes" the data, which would work standard but increase the complexity of the structures. Links ... sigh.
I use that a lot in qfcc (well, wrapped in error/warning/etc functions :))
I might not be experienced enough for this observation to be accurate, but I think some of the "Windows culture" of Visual Studio has some holes in it that working with some other platforms helps see through or whatever. As far as I can tell, Visual C++ seems very adverse to logging versus ... well ... <insert any other platform>.

I wanted the fprintf stuff to gain the advantage of console app like logging without actually having to write a console app. On another platform, I wouldn't have to do that.

This is barring some sort of info I am not aware of relating to Visual Studio.
#define field_offset(type,field) ((size_t)&(((type *)0)->field))

Of course, trying to read or write a struct field through a null pionter will get you a segfault, but it's perfectly valid to take the address of a field through a null pointer :). And yes, there is an offsetof macro, but at the time field_offset was written, offsetof wasn't very portable (ie, not all systems provided the macro).
Already abusing it. Looking to abuse it more. :D I think a couple of weeks ago, I inadvertently read that you can't necessarily memcmp structs! :o Which I have not had a problem with as most of the structs I would compare tend to be static (the memcmp issue is the padding, which is undefined). But that made me stop and think whether or not, as an example, that maybe in Quake some of what seem to be unnecessary memset(blah, 0, sizeof(struct) ) were in fact necessary or at least good coding practice.

re: multiplatform stuff ...

The engine stuff I am working on now, I have it running multiplatform with no #ifdefs in any .c file (except to exclude the entire file from compilation, this avoids the need to customize the project file per platform) Not even for compiler differences. And I've got all the compiler differences crammed into one file per compiler that doesn't relate to the operating system directly (those have own).

Code: Select all

#ifndef __ENVIRONMENT_VISUAL_STUDIO_6_H__
#define __ENVIRONMENT_VISUAL_STUDIO_6_H__

	#pragma message ("MSVC6 float math")
	#define sqrtf(x)	(float)sqrt(x)
	#define cosf(x)		(float)cos(x)
	#define sinf(x)		(float)sin(x)
	#define atanf(x)	(float)atan(x)
	#define tanf(x)		(float)tan(x)
	#define floorf(x)	(float)floor(x)
	#define ceilf(x)	(float)ceil(x)
	#define fabsf(x)	(float)fabs(x)
	#define powf(x,y)	(float)pow(x,y)
	#define atan2f(x,y) (float)atan2(x,y)
	#define minf(x,y)	(float)min(x,y)
	#define maxf(x,y)	(float)max(x,y)
	#define cbrtf(x)	(float)pow(x,(1./3.))	// MSVC6 doesn't have cube root function

#endif // ! __ENVIRONMENT_VISUAL_STUDIO_6_H__

#ifdef _WINDOWS_
	#ifndef __WINDOWS_VC6_BANDAGES__
		#define __WINDOWS_VC6_BANDAGES__
		// Bandages to cover things that must require a service pack for Visual Studio 6 ..
		// Except Microsoft doesn't make the service packs available any more so we work around

		// GetFileAttributes ...
		#define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
...
C is a very enjoyable language, in my opinion, after getting used to it. Seems to take getting a critical mass of how to plan stuff, which really in many ways doesn't have to do so much with the language itself as that C really makes you pay if you plan it out poorly.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Re: Mundane C tricks ...

Post by Spike »

regarding consoles, there's a OutputDebugStringA function available to windows programs that will dump the given string to a debug console within your IDE. There are also a few programs that can scrape these prints systemwide or something, thus avoiding the need to polute your pipes. This is especially important within libraries as you don't know if it already has a console anyway.

Regarding the absurd idea of cramming 64MB of data into an .exe - is it really any different from cramming 64MB of data into a .pak file?
In the unix world, people tend to write some sort of binary-data-file-to-c-byte-array file converter. Can just #include it then in an OS-agnostic way.

I use double pointers with single-linked lists loads. I tend to not use double-linked lists all that much.

Regarding memcmp+structs, better to ensure that your structs are always aligned properly, especially if you're reading/writing them to disk/network too. 32+64bit differences can be a pain though...
memsetting everything to 0 serves to protect against future changes. You can of course also use valgrind to find the issues, but there's always the probability that you'll miss half the occurences. Yay for large chunks of code.
GCC's memset inlining is meant to be able to be able to partially no-op the memset depending on which fields are later overwritten, however I assume this only really works with small structures that don't generate silly amounts of data or variable layouts etc.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Mundane C tricks ...

Post by Baker »

Spike wrote:memsetting everything to 0 serves to protect against future changes. You can of course also use valgrind to find the issues, but there's always the probability that you'll miss half the occurences. Yay for large chunks of code.
Looked up valgrind, I think I had the term before but didn't know what it was. Interesting.

re: I don't write structs to disk, mostly because I don't trust it work consistently (32/64 bit, padding, compiler, etc.). Still would like to figure out some magical way to convert at least a compile-time known struct (without pointers) to a text format of some sort.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Mundane C tricks ...

Post by Baker »

Spike wrote:Regarding the absurd idea of cramming 64MB of data into an .exe - is it really any different from cramming 64MB of data into a .pak file?
In the unix world, people tend to write some sort of binary-data-file-to-c-byte-array file converter. Can just #include it then in an OS-agnostic way.
Spike do you or anyone else know of a tool to quickly turn a file or binary data into a header file? I figure this has to be rather common.

/Still resists idea of a 64MB .exe a bit ... doesn't the entire binary get loaded into memory when you run an .exe on Windows --- and probably any other operating system --- or am I wrong?
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Re: Mundane C tricks ...

Post by Spike »

exes+binaries are generally memory mapped.
This means that data in your cdata section is only actually loaded when first read. Often this includes your writable data too, assuming it has no relocations inside.
When you run low on memory, these pages will normally be reread from the exe instead of your page file.
Yes, recompiling/relinking a binary can result in already-running instances crashing because the data in newly poked pages isn't what should be there when it was first run - I've seen linux gcc do this a fair bit a while ago. I assume ld unlinks rather than truncates nowadays (orphaning the old file+process images), to avoid this issue.

A windows resource is not mapped by default, but only when you use the builtins to read it.
When running a program from a remote/temporary file system (like a network share or a cd drive), windows will check the linker flags to see whether the entire exe should be read/copied, or if its okay to load on demand.

As you're already aware, you can use FindResource etc to load resources out of arbitary modules, exe files or dll files or whatever.
But you can also use BeginUpdateResource+UpdateResource+EndUpdateResource to edit the resources within an exe/dll/whatever without recompiling it.
You can only really do this because resources are not unlike data files within a pak - they are not memmapped by default, and do not consume address space nor memory until explicitly mapped.
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Re: Mundane C tricks ...

Post by mh »

I use OutputDebugString quite a bit (not as often as I should though); it's quite handy. With C++ some mild STL usage rocks - it saves you having to code and recode and recode the same thing over and over (a gripe I also have about the proliferation of different plain text file formats - every time I see another that needs it's own special dedicated text parser I honestly feel like screaming).

For VBO pointers you can do this:

Code: Select all

vertextype_t *vert = NULL;

glVertexAttribPointer (0, ..., vert->position);
glVertexAttribPointer (1, ..., vert->color);
glVertexAttribPointer (2, ..., vert->texcoord);
What's particularly neat about that is that it allows VBO code and non-VBO code to coexist more easily.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Mundane C tricks ...

Post by Baker »

mh wrote:(a gripe I also have about the proliferation of different plain text file formats - every time I see another that needs it's own special dedicated text parser I honestly feel like screaming).
Ok, here is a major gripe of mine --- and maybe of you more experienced guys know something helpful.

What would you recommend that is:
1. Platform neutral, doesn't require C++ (I guess it could be C++ if it can interface with C), doesn't just work on a single platform, doesn't require a DLL or API.
2. Doesn't require unicode (optional is ok).
3. Is fast, not overly complex like XML.
4. An average joe can view the data in a text editor and could make changes to it. (i.e. Doesn't require reading a book to understand).
5. Encodes and decodes whatever special characters itself without, say, me having to do it.
6. Returns something simple like a bool if it fails to parse.
This issue bothers me and I'm open to suggestions. I certainly would prefer to use a standardized text format. I guess I'm saying I so far haven't found one that follows the "Keep it simple, stupid" rule, clean, is robust, not massive overkill (like XML) and a nice code drop in.

I agree, but what do you have in mind or suggest I guess ...
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
taniwha
Posts: 401
Joined: Thu Jan 14, 2010 7:11 am
Contact:

Re: Mundane C tricks ...

Post by taniwha »

Baker: first, by "meh", all I meant was a lack of interest in windows specific stuff (no offense meant), but in retrospect, I realize it would be useful for improving windows support in QF. So, double sorry.

For the text format, take a look at qfplist (.h, .c, .py). The C version needs a couple of other modules from QF: dstring and hash. All three modules are reasonably well documented.(qfplist, hash tables and dstring). There is also a Ruamoko (Objective-QuakeC) interface, but I suspect you're not as interested in that :). The python version is standalone: not even one import, and returns python native types (dictionary, list, string, and whatever binascii returns (hmm, does that make it python 3 dependent?)). It raises PListError exceptions on errors (easily catchable).

Anyway, the C version is a nice code drop-in (3 .c files, 3 .h files) with maybe a little editing (mostly in the #include lines), the python version is standalone (at least for python 3), the code is well tested (QF flat-out relies on it, especially dstring and hash), and the format is pretty darn simple.
Leave others their otherness.
http://quakeforge.net/
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Mundane C tricks ...

Post by Baker »

taniwha wrote:Baker: first, by "meh", all I meant was a lack of interest in windows specific stuff (no offense meant), but in retrospect, I realize it would be useful for improving windows support in QF. So, double sorry.
No apologizes needed :D
For the text format, take a look at qfplist (.h, .c, .py). The C version needs a couple of other modules from QF: dstring and hash. All three modules are reasonably well documented.
It looks very solid and I see you handle all the escape sequences, I believe this is your key/value code that saves/load to either HTML or basic XML and encode/decodes. A non-nitpick is I'd have to unravel the dependency tree of all QF includes it seems to require, which I suspect requires other source files as well :( . This isn't a nitpick because no doubt the quality of the code is A+++ and certainly is road-tested. (*)

I noticed the use of alloca. I'm not familar with alloca and not sure I understand the implications of allocating to the stack. I actually didn't know that was possible, is that some sort of allocation that gets "auto-freed" after the function exits? :?:

(*) Someone may view the cut/paste desire as lazy. The cut-paste desire for a text parser isn't lazy, rather it goes to time of implementation and accessibility of implementation (i.e. can newbie X or Y also do this?). For text parsing, the time and accessibility of implementation bears of whether or not it can easily go to widespread use which includes the inexperienced.

However, I might give it a shot and try to tutorialize the implementation at some point ... the code seems robust and if the input and output is what I think, that's A+++.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Mundane C tricks ...

Post by Baker »

re: alloca ... haha ... nice! Should play nice with setjmp/longjmp. That's a C-trick in its own right! Never had heard of it before poking around your code.

EDIT: maybe alloca isn't so great ...

Code: Select all

void DoSomething() {
   wchar_t* pStr = alloca(100);
   //......
}

In the implementation file:

void Process() {
   for (i = 0; i < 1000000; i++) {
     DoSomething();
   }
}
So what happened was the compiler inlined DoSomething function and all the stack allocations were happening inside Process() function and thus blowing the stack up. (link)
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
taniwha
Posts: 401
Joined: Thu Jan 14, 2010 7:11 am
Contact:

Re: Mundane C tricks ...

Post by taniwha »

I'm not sure what happens with alloca in an inlined function. I would assume the compiler would reset the stack pointer at the end of the inlined function, but it looks like gcc refuses to inline the function.
gcc docs wrote: Note that certain usages in a function definition can make it unsuitable for inline substitution. Among these usages are: variadic functions, use of alloca, use of variable-length data types (see Variable Length), use of computed goto (see Labels as Values), use of nonlocal goto, and nested functions (see Nested Functions). Using -Winline warns when a function marked inline could not be substituted, and gives the reason for the failure.
As for the QF header dependencies: it's not as bad as it looks: mathlib.h is for min/max/bounds, compat mabe strequal, sys.h for Sys_Printf and Sys_Error: all easy to replace.
Leave others their otherness.
http://quakeforge.net/
taniwha
Posts: 401
Joined: Thu Jan 14, 2010 7:11 am
Contact:

Re: Mundane C tricks ...

Post by taniwha »

An update for inlined use of alloca: it's possible to force gcc to inline such a function by using __attribute__((always_inline)). I just checked the generated assembly, and gcc resets the stack pointer after the inlined code.

That said, I wouldn't trust Microsoft to get it right :P.
Leave others their otherness.
http://quakeforge.net/
taniwha
Posts: 401
Joined: Thu Jan 14, 2010 7:11 am
Contact:

Re: Mundane C tricks ...

Post by taniwha »

Oh, and the trick for tangled #includes: nuke them all :). You can then add new ones as necessary. If you get an error in a .h file, add the #include there, otherwise in the .c file.
Leave others their otherness.
http://quakeforge.net/
Post Reply