6

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);
Isaac Turner
  • 2,673
  • 1
  • 26
  • 25
  • 2
    You have to better define "portable". C is everywhere; I don't think embedded platform X's compiler will be happy with any hack that might be posted. So narrow it down a bit. – Jon Aug 31 '12 at 11:57
  • Standard C does not allow nested functions. I can not think of a way of achieving this in Standard C, but why is it needed even? This in fact will be somewhat confusion! A regular function for `cmp_func` should be as good as what you have shown. – Curious Aug 31 '12 at 12:04
  • I've put the portable wrapper for qsort_r at: https://github.com/noporpoise/sort_r – Isaac Turner Feb 05 '15 at 14:51

5 Answers5

12

Just for fun (and to answer the original question), yes it is entirely possible to write nested functions in standard-compliant C99 using the macro system to "untangle" the nested version of your code. Here's one possible implementation: https://github.com/Leushenko/C99-Lambda

With it, you can write abominations like this:

typedef int(* fptr)(int);
func(fptr, someFunc, (void) {
    return fn(int, (int a), {
        fptr f = fn(int, (int b), { return b * 6; });
        return a * f(a + 1);
    });
})

Let's be very clear about something though: this is the absolute worst way to write this sort of code in C. If you ever find yourself in a position where you actually need to use a macro library to write code like this, quit your job as a programmer and become a farmer. Use this in production and your colleagues will probably murder you in your sleep.

Also, hilariously enough, even though it's technically standard-compliant, the only compilers with a preprocessor that can handle the sheer weight of that many macros are GCC and Clang anyway.

Alex Celeste
  • 12,824
  • 10
  • 46
  • 89
  • 1
    Well, *technically*, if your preprocessor won't cut it, you can feed your source code to your favourite `cpp` before using your compiler. (And I doubt that this code will be a problem for most preprocessors. It's much more tame than some macro code I've seen in reality). – nneonneo Feb 08 '13 at 21:42
  • 2
    Excellent answer, though I'd argue that if you feel the need to go this far with preprocessing you're probably better off writing a full-fledged source-to-source translator and not calling your language C anymore. – Nate C-K Feb 03 '15 at 15:17
4

There is no portable way to write nested functions in C simply because the C standard does not allow nested functions.
Macros won't help you much here because they are evaluated by the preprocessor and the compiler will still see the code which nests functions and flag an error.

Alok Save
  • 202,538
  • 53
  • 430
  • 533
1

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 funciton - invert region (inclusive)
int invert_start, invert_end;

// Comparitor that uses global variables (invert_start, invert_end) as paramaters
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
Community
  • 1
  • 1
Isaac Turner
  • 2,673
  • 1
  • 26
  • 25
  • This is a clever implementation. Certainly it fits the bill. If you found yourself needing this, though, I'd say you'd probably be better off copying someone's qsort implementation (maybe from FreeBSD?) and adding an extra void* function argument so you can pass extra data to the function via the stack. – Nate C-K Feb 03 '15 at 15:20
0

Nested functions aren't in the C standard, but it is a gcc extension (when the flag -fnested-functions is actived). Also, you can use a static function (cmp_func) and a extra-parameter (reverse) to do the same thing.

md5
  • 23,373
  • 3
  • 44
  • 93
  • I don't see how you'd pass the extra parameter (`reverse`) to the compare function (`cmp_func`) without using global variables. – Isaac Turner Aug 31 '12 at 15:21
  • 1
    But you can't use global variables and a mutex ? – md5 Aug 31 '12 at 15:24
  • 1
    Re passing extra parameters to comparison functions: There is a non-standard function called `qsort_r` (or sometimes `qsort_s`) in most libcs, but it's extremely hard to use cross-platform because each libc puts the arguments in a different order (for historical reasons that boil down to stupidity on the parts of all parties involved). See this other question: http://stackoverflow.com/questions/4300896/how-portable-is-the-re-entrant-qsort-r-function-compared-to-qsort – Quuxplusone Aug 31 '12 at 18:35
0

Why go to the trouble of nested functions and globals at all? The 100% portable (even to K&R) solution is simply to use two different functions, one to sort in regular order, the other in reverse, and then call it as

qsort(list, 8, sizeof(int), reverse ? cmp_func_reverse : cmp_func);

Note: no need to take the address of a function with &.

Jens
  • 69,818
  • 15
  • 125
  • 179
  • +1 This one has the added benefit of sparing n*log2 n reads of the global variable which can be relatively expensive on some architectures (64bit RISCS). – Patrick Schlüter Sep 14 '12 at 13:09
  • 1
    It's fine for the toy example given initially, but what if the sort function requires one or more integers as parameters? That's 2^32 cases! Try implementing the example given in the accepted solution with your approach: http://stackoverflow.com/a/12284195/431087 – Isaac Turner Sep 14 '12 at 14:49
  • @IsaacTurner Use block scope static variables in the compare function. To assign to them, call with the first pointer being `NULL` and the second being a pointer to a struct to fill in all static vars. A bit of a hack, but not impossible. – Jens Sep 14 '12 at 14:58
  • @Jens static variables are an interesting suggestion - thanks. Is it possible to do in a thread-safe manner (as stated in the original question) in a way that's cleaner than the accepted solution? For that I had to use a mutex. – Isaac Turner Sep 15 '12 at 10:30
  • @IsaacTurner Since there is only one copy of the static variables I can't think of a way to avoid mutexes in a multithreaded environment in the general case. – Jens Sep 15 '12 at 10:37