【问题标题】:Portable nested functions in CC中的可移植嵌套函数
【发布时间】:2012-08-26 05:50:30
【问题描述】:

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

我知道 gcc 仅支持作为非标准扩展的嵌套函数,而 clang 仅支持块 - 但是有没有办法编写可以同时使用标准 C 和 MACROS 编译的代码?

如果不可能 - 最好的解决方法是什么?例如,如何实现以下带参数的可移植版本? 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 中的 Blocks 组合一个类似的示例。理想情况下,解决方案应该是线程安全的(因此避免使用全局变量)。

编辑:为清楚起见,我们假设“标准”是指 C99。上面是一个简单的例子。我所追求的是一种需要一些参数的 C99 方法。在这里,它只使用一个 char 作为布尔值,但我正在寻求一个需要多个整数等的解决方案。看起来如果没有全局变量,这可能是不可能的。

编辑 2: 我意识到将 void 指针与函数指针一起传递可以让您完成所有可以使用嵌套函数完成的事情。感谢@Quuxplusone 建议qsort_rqsort_s。我试图在qsort_rqsort_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);

【问题讨论】:

  • 你必须更好地定义“便携”。 C无处不在;我认为嵌入式平台 X 的编译器不会对可能发布的任何 hack 感到满意。所以缩小一点。
  • 标准 C 不允许嵌套函数。我想不出在标准 C 中实现这一点的方法,但为什么甚至需要它呢?这实际上会有些混乱! cmp_func 的常规函数​​应该和你展示的一样好。
  • 我已将 qsort_r 的便携式包装器放在:github.com/noporpoise/sort_r

标签: c gcc clang nested-function


【解决方案1】:

只是为了好玩(并回答原始问题),是的,完全可以使用宏系统在符合标准的 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。

【讨论】:

  • 好吧,技术上,如果你的预处理器不能解决它,你可以在使用你的编译器之前将你的源代码提供给你最喜欢的cpp。 (而且我怀疑这段代码对于大多数预处理器来说会是一个问题。它比我在现实中看到的一些宏代码要温和得多)。
  • 很好的答案,尽管我认为如果你觉得有必要在预处理方面走这么远,你最好编写一个成熟的源到源翻译器而不是调用你的语言C 了。
【解决方案2】:

因为 C 标准不允许嵌套函数,所以没有可移植的方式在 C 中编写嵌套函数。
宏在这里对您没有多大帮助,因为它们是由预处理器评估的,编译器仍然会看到嵌套函数并标记错误的代码。

【讨论】:

    【解决方案3】:

    根据@Kirilenko here 的建议,我想出了一个解决方案,使用全局变量和互斥体将参数传递给排序比较器函数。这种方法是线程安全的,可以用嵌套函数完成所有事情,并且应该可以在编译器之间移植。

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

    // 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* 函数参数,这样您就可以通过堆栈将额外的数据传递给函数.
    【解决方案4】:

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

    【讨论】:

    • 我不明白你如何在不使用全局变量的情况下将额外参数 (reverse) 传递给比较函数 (cmp_func)。
    • 但是你不能使用全局变量和互斥锁?
    • 将额外的参数重新传递给比较函数:在大多数 libcs​​ 中都有一个名为 qsort_r(或有时是 qsort_s)的非标准函数,但跨平台使用非常困难,因为每个 libc将论点以不同的顺序排列(由于历史原因,归结为所有相关方的愚蠢)。请参阅另一个问题:stackoverflow.com/questions/4300896/…
    【解决方案5】:

    为什么还要麻烦嵌套函数和全局变量呢? 100% 可移植(甚至对 K&R)的解决方案就是简单地使用两个不同的函数,一个按正序排序,另一个按逆序排序,然后调用它为

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

    注意:函数的地址不用&amp;

    【讨论】:

    • +1 这个有额外的好处,可以节省对全局变量的 n*log2 n 次读取,这在某些架构(64 位 RISCS)上可能相对昂贵。
    • 最初给出的玩具示例很好,但是如果排序函数需要一个或多个整数作为参数怎么办?那是 2^32 箱!尝试使用您的方法实现已接受的解决方案中给出的示例:stackoverflow.com/a/12284195/431087
    • @IsaacTurner 在比较函数中使用块范围静态变量。要分配给它们,调用第一个指针是NULL,第二个是指向结构的指针以填充所有静态变量。有点小技巧,但并非不可能。
    • @Jens 静态变量是一个有趣的建议 - 谢谢。是否可以以比公认解决方案更清洁的方式以线程安全的方式(如原始问题中所述)进行操作?为此,我不得不使用互斥锁。
    • @IsaacTurner 由于静态变量只有一个副本,因此在一般情况下,我想不出在多线程环境中避免互斥锁的方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-03-10
    • 2010-10-02
    • 2011-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-06
    相关资源
    最近更新 更多