Is it possible to write portable C code using nested functions/blocks?
I understand that gcc only supports nested functions as an non-standard extension, and clang only supports blocks - but is there a way to write code that will compile on both using standard C with MACROS?
If it is not possible - what is the best work around? As an example, how would one implement a portable version of the following sort that takes a parameter? Trivial example in GCC:
int main(int argc, char*[] argv)
{
char reverse = 0;
int cmp_func(const void *a, const void *b)
{
const int* aa = (const int)a;
const int* bb = (const int)b;
return (reverse) ? aa - bb : bb - aa;
}
int list[8] = {1,2,3,4,5,20,100,200};
qsort(list, 8, sizeof(int), &cmp_func);
}
A similar example could be put together using Blocks in Clang. Ideally the solution should be thread-safe (so avoid global variables).
Edit: For clarity, lets assume "standard" means C99. The above is a trivial example. What I'm after is a C99 approach to a sort that requires some parameters. Here it just uses a char as a boolean, but I'm after a solution that would take multiple integers etc. It looks like this might not be possible without global variables.
Edit 2: I realised that passing a void pointer along with a function pointer enables you to do everything that can be done with nested functions. Thanks to @Quuxplusone for suggesting qsort_r
and qsort_s
. I've tried to put together a portable wrapper on qsort_r
and qsort_s
. It takes a comparator function and a void pointer to store state in, thus removing the dependency on nested functions for intricate sorting algorithms -- so you can compile with both GCC and Clang.
typedef struct
{
void *arg;
int (*compar)(const void *a1, const void *a2, void *aarg);
} SortStruct;
int cmp_switch(void *s, const void *aa, const void *bb)
{
SortStruct *ss = (SortStruct*)s;
return (ss->compar)(aa, bb, ss->arg);
}
void sort_r(void *base, size_t nel, size_t width,
int (*compar)(const void *a1, const void *a2, void *aarg), void *arg)
{
#if (defined _GNU_SOURCE || defined __GNU__ || defined __linux__)
qsort_r(base, nel, width, compar, arg);
#elif (defined __APPLE__ || defined __MACH__ || defined __DARWIN__ || \
defined __FREEBSD__ || defined __BSD__ || \
defined OpenBSD3_1 || defined OpenBSD3_9)
SortStruct tmp = {arg, compar};
qsort_r(base, nel, width, &tmp, &cmp_switch);
#elif (defined _WIN32 || defined _WIN64 || defined __WINDOWS__)
SortStruct tmp = {arg, compar};
qsort_s(*base, nel, width, &cmp_switch, &tmp);
#else
#error Cannot detect operating system
#endif
}
Note: I haven't tested this on many platforms, so please let me know if you see a bug / this doesn't work on your machine.
As an example of usage, I've implemented the same sort as in the chosen answer:
int sort_r_cmp(const void *aa, const void *bb, void *arg)
{
const int *a = aa, *b = bb, *p = arg;
int cmp = *a - *b;
int inv_start = p[0], inv_end = p[1];
char norm = (*a < inv_start || *a > inv_end || *b < inv_start || *b > inv_end);
return norm ? cmp : -cmp;
}
int arr[18] = {1, 5, 28, 4, 3, 2, 10, 20, 18, 25, 21, 29, 34, 35, 14, 100, 27, 19};
int p[] = {20, 30};
sort_r(arr, 18, sizeof(int), sort_r_cmp, p);
Following the suggestion by @Kirilenko here, I've come up with a solution using global variables and a mutex to pass parameters to a sort comparator function. This approach is thread-safe, can do everything achieved with nested functions and should be portable between compilers.
This example sorts a list of integers, but inverts the sort for a given region.
// define lock for sort parameters
pthread_mutex_t lock;
// Parameters used in sort function - invert region (inclusive)
int invert_start, invert_end;
// Comparison that uses global variables (invert_start, invert_end) as parameters
int cmp_func(const void *a, const void *b)
{
const int aa = *(const int*)a;
const int bb = *(const int*)b;
if(aa < invert_start || aa > invert_end ||
bb < invert_start || bb > invert_end)
{
return aa - bb;
}
else
{
return bb - aa;
}
}
void sort_things(int* arr, int arr_len, int inv_start, int inv_end)
{
// Mutex lock
pthread_mutex_lock(&lock);
// Set params
invert_start = inv_start;
invert_end = inv_end;
// do sort
qsort(arr, arr_len, sizeof(*arr), &cmp_func);
// Mutex free
pthread_mutex_unlock(&lock);
}
Example results:
input: 1 5 28 4 3 2 10 20 18 25 21 29 34 35 14 100 27 19
invert_start = 20, invert_end = 30
output: 1 2 3 4 5 10 14 18 19 29 28 27 25 21 20 34 35 100