Полноценной замены вложенным функциям в языке программирования Си нет, но есть несколько способов, как их можно симулировать. Чаще всего в вложенных функциях нам важно то, что код определяется там же, где передаётся в качестве функции обратного вызова. Иногда этот код бывает настолько мал, что выносить его в отдельную функцию в глобальной области видимости смысла нет. Например, для сортировки массива по возрастанию с помощью функции типа qsort чаще всего достаточно такого кода: return e1 - e2;. Вынести его в отдельную функцию в глобальной области видимости, а затем ещё придумывать корректное название — так себе удовольствие. Вложенные функции, добавленные в GCC как расширение, могли бы решить эту проблему, но такой код не будет работать на других компиляторах языка Си.

Способ, который описан в этой статье, основан на возврате из функции вместо обратного вызова и работает примерно так же, как функции-генераторы или цикл событий. Для начала взглянем на код и сравним его с вложенными функциями из GCC.

int array[] = {1, 4, 2, 5, 2, 2, 4, 6};
int n = 8;

//GCC расширение
int removePred(int e){
	return e % 2;
}
n = remove_if(array, n, removePred);


{func(remove_if, array, n){
	ths.ret = ths.e % 2;
} n = ths.res; }

Здесь мы использовали макрос func, который сокращает нашу запись, а вот во что он превратиться после обработки препроцессором.

{
  struct remove_if ths = {0, array, n};
  while(remove_if(&ths)){
    ths.ret = ths.e % 2 == 1;
  }
  n = ths.res;
}

Логика для выявления нечётных чисел расположена внутри вызывающей функции, а чтобы получить к ней доступ, наша функция remove_if возвращает управление, запоминая состояние внутри структуры ths, а возвращаемым значением сообщает, что её работа не окончена. Фигурные скобки используются для ограничения области видимости, чтобы предотвратить конфликт имен, но можно избавиться от них — тогда наш код примет вид.

func(remove_if, obj, array, n){
	obj.ret = obj.e % 2 == 1;
}
n = obj.res;

Весь код будет выглядеть так

#include <stdio.h>

struct remove_if{
  int st;
  int *arr;
  int n;
  int e;
  int res;
  int ret;
  int i;
  int ofs;
};

/* Суть алгоритма ниже в этом
int remove_if(Callback callback, int* array, int n){
  int ofs = 0, i = 0;
  for(; i < n; i++){
    if(callback(array[i]){
      ofs++;
    } else {
      arr[i - ofs] = arr[i];
    }
  }
  return i - ofs;
}
*/

int remove_if(struct remove_if *r){
  if(r->st == 0){
    r->st = 1;
    r->i = 0;
    r->ofs = 0;
  }else{
    if(r->ret){
      r->ofs++;
    }else{
      r->arr[r->i - r->ofs] = r->arr[r->i];
    }
    r->i++;
  }
  if(r->i == r->n){
    r->res = r->i - r->ofs;
    return 0;
  }
  r->e = r->arr[r->i];
  return 1;
}

#define func(fn, ...)\
  struct fn ths = {0, __VA_ARGS__};\
  while(fn(&ths))

void printArray(int *array, int n){
  for(int i = 0; i < n; i++){
    printf("%d, ", array[i]);
  }
  printf("\n");
}

int main(){

  int array[10] = [5, 2, 8, 6, 5, 2, 1, 1, 2, 3];
  int n = 10;

  {func(remove_if, array, n){
    ths.ret = ths.e % 2 == 1;
  } n = ths.res; }

  printArray(array, n);

  return 0;
}

Функция для удаления элементов выглядит немного необычно, но производительность будет примерно такой же, как при использовании вложенных функций, так как операций происходит примерно одинаковое количество, вызовов и возвращений из функций происходит тоже равное количество. Возможно наш код лишится некоторых оптимизаций, но не критических, в случае необходимости мы можем даже использовать inline. Но у этого способа есть другой недостаток  —  у нас нет указателя на функцию, следовательно мы не можем запомнить её, чтобы вызвать позже. Также способ немного необычный, но в этой необычности есть своя логика и чтобы разобраться в ней, взглянем на следующий код.

typedef void (*func)();

int template(func f, int a1, int a2){
  int b, c, d;
  //код до цикла
  while(/*условие выхода из цикла*/0){
    //код до вызова обратной функции внутри цикла
    int ret = f();
    //код после вызова обратной функции внутри цикла
  }
  //код после цикла
  return 5; //возвращаемое значение
}

struct template{
  int st; //состояние первый вызов = 0, повторный вызов = 1
  int a1, a2; //аргументы
  int a, b, c; //переменные
  int ret; //возвращаемое обратной функцией значение
  int res; //возвращаемое значение
};

int template(struct template *t){
  if(t->st == 0){
    //инициализация
    //код до цикла
  }else{
    //код после вызова обратной функции внутри цикла
  }
  if(/*условие выхода из цикла*/0){
    //код после цикла
    t->res = 5;//возвращаемое значение
    return 0;
  }
  //код до вызова обратной функции внутри цикла
  return 1;
}

Здесь сравнивается простой шаблон, по которому мы можем создать функцию. Комментарии показывают, какая часть соответствует которой. Все переменные мы храним в структуре, так как они нам нужный при повторном вызове. state указывает, это первый вызов или повторный, если простыми словами. Это простой шаблон с одним циклом и вызовом внутри цикла. Его можно расширить под нужды. В следующем коде собраны несколько алгоритмов: remove_if, map и сортировка пузырьком bsort.

#include <stdio.h>
#include <stdlib.h>

struct remove_if{
  int st;
  int *arr;
  int n;
  int e;
  int res;
  int ret;
  int i;
  int ofs;
};

int remove_if(struct remove_if *r){
  if(r->st == 0){
    r->st = 1;
    r->i = 0;
    r->ofs = 0;
  }else{
    if(r->ret){
      r->ofs++;
    }else{
      r->arr[r->i - r->ofs] = r->arr[r->i];
    }
    r->i++;
  }
  if(r->i == r->n){
    r->res = r->i - r->ofs;
    return 0;
  }
  r->e = r->arr[r->i];
  return 1;
}

struct map{
  int st;
  int *arr;
  int n;
  int ret;
  int *res;
  int i, e;
};

int map(struct map* m){
  if(m->st == 0){
    m->i = 0;
    m->st = 1;
    m->res = malloc(sizeof(int) * m->n);
  }else{
    m->res[m->i] = m->ret;
    m->i++;
  }
  if(m->i == m->n){
    return 0;
  }
  m->e = m->arr[m->i];
  return 1;
}

struct bsort{
  int st;
  int *arr;
  int n;
  int i, j;
  int e1, e2;
  int ret;
};

int bsort(struct bsort *s){
  if(s->st == 0){
    s->st = 1;
    s->i = 1;
    s->j = 0;
  }else{
    if(s->ret > 0){
      int tmp = s->arr[s->j];
      s->arr[s->j] = s->arr[s->j + 1];
      s->arr[s->j + 1] = tmp;
    }
    s->j++;
    if(s->j >= s->n - s->i){
      s->j = 0;
      s->i++;
    }
  }
  if(s->i >= s->n){
    return 0;
  }
  s->e1 = s->arr[s->j];
  s->e2 = s->arr[s->j + 1];
  return 1;
}

#define func(fn, ...)\
  struct fn ths = {0, __VA_ARGS__};\
  while(fn(&ths))

void printArray(int *array, int n){
  for(int i = 0; i < n; i++){
    printf("%d, ", array[i]);
  }
  printf("\n");
}

int main(){

  int array[10] = {5, 2, 8, 6, 5, 2, 1, 1, 2, 3};
  int n = 10;

  printArray(array, n);

  {func(remove_if, array, n){
    ths.ret = ths.e % 2 == 1;
  } n = ths.res; }

  printArray(array, n);

  {func(bsort, array, n){
    ths.ret = ths.e1 - ths.e2;
  }}

  printArray(array, n);

  int *arr2;

  {func(map, array, n){
    ths.ret = ths.e * 2;
  } arr2 = ths.res; }

  printArray(arr2, n);

  free(arr2);

  return 0;
}

Многие вещи в языке программирования Си создаются на уровне кода: сопрограммы, исключения и даже ООП. Такие конструкции почти всегда уступают встроенным средствам как по гибкости, так и по производительности. Иногда они используются в крупных проектах, как например GObject, а иногда остаются творческими проектами, которые как минимум интересны с образовательной точки зрения, так как именно здесь раскрывается вся гибкость языка и его тонкости.