C语言中的可移植嵌套函数

7

使用嵌套函数/块编写可移植的C代码是否可能?

我知道gcc只支持嵌套函数作为非标准扩展,而clang只支持块 - 但是是否有一种方法可以使用MACROS编写能够在两者上编译的标准C代码?

如果不可能-最佳解决方法是什么?例如,如何实现接受参数的以下排序的可移植版本?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);
}

使用Clang中的块可以组合一个类似的示例。理想情况下,解决方案应该是线程安全的(因此避免使用全局变量)。
编辑:为了清晰起见,假设“标准”表示C99。上面是一个微不足道的例子。我想要的是一种需要一些参数的排序的C99方法。这里只使用一个char作为布尔值,但我想要一个可以使用多个整数等的解决方案。看起来这可能没有全局变量是不可能的。
编辑2:我意识到,通过传递void指针和函数指针,您可以执行可以使用嵌套函数完成的所有操作。感谢@Quuxplusone建议的qsort_r和qsort_s。我尝试组合了一个便携式包装器,用于qsort_r和qsort_s。它接受比较器函数和一个void指针以存储状态,从而消除了对嵌套函数进行复杂排序算法的依赖,因此您可以同时使用GCC和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
}

注意:我还没有在许多平台上测试过这个,所以如果您在您的设备上发现了错误/无法工作,请让我知道。
举个例子,我已经实现了与选择的答案相同的排序方法。
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);

2
你需要更好地定义“可移植性”。C语言无处不在;我认为嵌入式平台X的编译器不会对任何可能发布的黑客行为感到满意。所以请将其缩小范围。 - Jon
标准C不允许嵌套函数。我无法想到在标准C中实现这一点的方法,但为什么需要呢?实际上这会有些混乱!一个常规的cmp_func函数应该和你展示的一样好。 - Curious
我已将qsort_r的便携式包装器放在以下网址:https://github.com/noporpoise/sort_r - Isaac Turner
5个回答

12

仅供娱乐(并且回答原问题),是完全可能使用宏系统在符合标准的C99中编写嵌套函数以“解开”代码的嵌套版本。这里是一个可能的实现:https://github.com/Leushenko/C99-Lambda

使用它,你可以编写像这样的可怕代码:

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);
    });
})

让我们非常明确一件事:这是使用C语言编写此类代码的绝对最差方法。如果你发现自己处于必须使用宏库编写此类代码的位置,请辞去作为程序员的工作,成为一名农民。如果在生产环境中使用此代码,你的同事们可能会在你入睡时谋杀你。

另外,令人捧腹的是,尽管严格上来说符合标准,但唯一能够处理如此多宏的预处理器编译器是GCC和Clang。


1
从技术上讲,如果您的预处理器无法胜任,您可以在使用编译器之前将源代码提供给您喜爱的 cpp。 (我怀疑这段代码对大多数预处理器来说不会是问题。它比我在实际中看到的一些宏代码要温和得多)。 - nneonneo
2
非常好的回答,尽管我认为如果你觉得需要在预处理方面走这么远,那么最好写一个完整的源到源代码转换器,而不再称你的语言为C。 - Nate C-K

4

C语言标准不允许嵌套函数,因此在C中编写嵌套函数没有可移植性。

宏并不能解决这个问题,因为它们由预处理器评估,编译器仍然会看到嵌套函数的代码,并标记一个错误。


1
为什么要费心去使用嵌套函数和全局变量呢?100%可移植(甚至适用于K&R)的解决方案是使用两个不同的函数,一个按常规顺序排序,另一个按相反顺序排序,然后调用它。
qsort(list, 8, sizeof(int), reverse ? cmp_func_reverse : cmp_func);

注意:不需要使用&获取函数的地址。

+1 这个方法还有一个额外的好处,就是避免了对全局变量进行 n*log2 n 次读取,这在某些架构(如64位RISCS)上可能相对昂贵。 - Patrick Schlüter
1
最初给出的玩具示例可以接受,但如果排序函数需要一个或多个整数作为参数呢?那就有2^32种情况!请尝试使用您的方法实现已接受解决方案中给出的示例:https://dev59.com/mmfWa4cB1Zd3GeqPlutc#12284195 - Isaac Turner
@IsaacTurner 在比较函数中使用块作用域静态变量。要对它们进行赋值,调用时第一个指针为“NULL”,第二个指针为指向填充所有静态变量的结构体的指针。这有点像黑客技巧,但并非不可能。 - Jens
@Jens 静态变量是一个有趣的建议 - 谢谢。是否有可能以比已接受的解决方案更清晰的方式以线程安全的方式实现?为此,我不得不使用互斥锁。 - Isaac Turner
@IsaacTurner,由于静态变量只有一个副本,我无法想到一种在多线程环境中避免互斥的通用方法。 - Jens

1

根据@Kirilenko 这里的建议,我使用全局变量和互斥锁来传递参数到排序比较函数中,提出了一种解决方案。这种方法是线程安全的,可以完成嵌套函数所能实现的所有功能,并且应该可以在不同编译器之间移植。

此示例对整数列表进行排序,但会针对给定区域反转排序。

// 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);
}

示例结果:

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

这是一个聪明的实现。当然它符合要求。但如果你发现自己需要这个,我会说你最好复制某人的qsort实现(也许来自FreeBSD?),并添加一个额外的void*函数参数,以便通过堆栈传递额外的数据到函数中。 - Nate C-K

0

嵌套函数不在C标准中,但它是gcc的扩展(当标志-fnested-functions被激活时)。此外,您可以使用静态函数(cmp_func)和额外参数(reverse)来完成相同的事情。


我不知道如何在不使用全局变量的情况下将额外参数(reverse)传递给比较函数(cmp_func)。 - Isaac Turner
1
但是您不能使用全局变量和互斥锁吗? - md5
1
关于向比较函数传递额外参数:大多数libc中都有一个非标准函数叫做qsort_r(有时也叫qsort_s),但由于每个libc将参数放置的顺序不同(由于历史原因,这归结为所有相关方的愚蠢),所以跨平台使用非常困难。请参见此其他问题:https://dev59.com/t2855IYBdhLWcg3wg0nC - Quuxplusone

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接