stb_truetype and STBTT_malloc/STBTT_free

Hey all,

I just watched the episodes on fonts, and decided to implement Sean's truetype in my Delphi project. Everything is converting nicely, except that I don't seem to understand the alloc and free macros, defined and used as follow:

#define STBTT_malloc(x,u) ((void)(u),malloc(x))
#define STBTT_free(x,u) ((void)(u),free(x))

vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata);

I don't understand what the macro does with the info->userdata part when it's expanded. Could someone clarify this? How would this allocation be written without the macro, because I really don't get what its doing, unless its putting the pointer of the allocated memory in both vertices and info->userdata, which might be wrong.

Thanks for any help guys!
Remember that C preprocessor is dumb text replacement, it doesn't care about C semantics it just replaces one text with another. So that line expands to following C code:
1
vertices = (stbtt_vertex *) ((void)(info->userdata),malloc(m * sizeof(vertices[0])));

What does this mean?

C language has comma operator. if you write a,b,c then what compiler does is evaluate a, discards the result, then evaluate b, discards the result and then evaluate c which is the "result" of a,b,c expression.

For example:
1
x = (a = 2, c=3, b=a);

This is equivalent to writing following code:
1
2
3
4
a = 2;
c=3;
b=a;
x = (b=a); // or x=b

So back you the code line from your post, it actually means following:
1
2
(void)(info->userdata);
vertices = (stbtt_vertex *) malloc(m * sizeof(vertices[0]));

First is harmless statement with no side effects that doesn't do anything, and second line is simple call to malloc which I think you understand.

So why do this kind of stuff with comma operator and userdata?
First of all - this comma operator is nice way how to discard unneeded expression, but still make compiler to evaluate it. Why to evaluate it? To avoid compiler warnings about unused variables. Imagine if code would be something like this:
1
2
int user = ...;
void* ptr = STBTT_malloc(100, user);

if STBTT_malloc would be only "#define STBTT_malloc(x) malloc(x)" then it compiler would complain that "int user" variable is never used. Which would be very annoying!

So why to do this thing with "u" userdata argument? It is there to support generic allocators. Sure malloc/free is very simple and requires only one argument, but you could imagine more universal allocator would need to support source from where to allocate memory (like arena/heap or something). So it could probably has following signature:
1
2
void* allocate(size_t size, void* memory_heap);
void deallocate(void* pointer, void* memory_heap);

This is actually very common thing in C - to add extra void* argument to callbacks that allows user to pass additional data to system and get it back in callback.

Here's simple example from Win32 API - EnumWindows. It enumerates all windows in system.
You call it by passing callback to function which will receive info about windows and extra argument (lParam), which will be passed back to your callback. Example usage:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
BOOL CALLBACK MyCallback(HWND hwnd, LPARAM lParam)
{
    if (hwnd is window I'm looking for)
    {
        HWND* result = (HWND*)lParam;
        *result = hwnd;
        return FALSE; // stop enumeration
    }
    return TRUE; // continue enumeration
}

...

HWND result;
EnumWindows(&MyCallback, (LPARAM)&result); // this passes HWND* in lparam
// now result contains HWND that was written in callback

This makes code a bit cleaner so you don't need to use global variables which are written in one place and read in another place. It also helps to keep code thread-safe.

Edited by Mārtiņš Možeiko on Reason: typos
Thank you for this great answer, Mārtiņš, I very much appreciate it and it explains everything I would ever think to ask!
We need some sort of reputation or upvoting on these forums. You're a bamf mmozeiko