About
RSS

Bit Focus


GCC 的花招: 嵌套函数

    这里所说的 GCC 不是 GNU Compiler Collection, 而是单单指代 GNU C Compiler. 这篇文章并不打算讲标准 C 中的任何东西, 而是要聊聊一个 GCC 的特性: 嵌套函数.
    先来个例子, C 版的 for_each 结合嵌套函数 (必须存为 .c 文件, 使用 gcc 编译才能通过, g++ 压力大)
#include <stdio.h>

void for_each(int* begin, int const* end, void (* fn)(int*))
{
    while (begin < end) {
        fn(begin++);
    }
}

#define ARRAY_SIZE 5

int main(void)
{
    int a[ARRAY_SIZE];
    int n;
    void read(int* x)
    {
        scanf("%d", x);
    }
    for_each(a, a + ARRAY_SIZE, read);
    scanf("%d", &n);

    void add(int* x)
    {
        *x = *x + n;
    }
    for_each(a, a + ARRAY_SIZE, add);

    void print(int* x)
    {
        printf("%d ", *x);
    }
    for_each(a, a + ARRAY_SIZE, print);
    printf("end\n");
    return 0;
}
    这代码的紧凑程度虽然跟闭包没得比, 但是使用与定义能放在一起, 就算是一大进步了. 另一个福利在 add 函数, 它引用了一个局部变量 n, 是在外层函数 main 中定义的. 而 n 的使用并不在 main 的可控范围内, 也许它绕过 for_each 的栈帧, 再继续向上被 add 引用. 这听起来像魔法一样, 幸而 gcc 是个开源软件, 不必担心使用这个特性时会让自己的灵魂落入某个兼职程序员的邪恶巫师手中. 但是这到底是如何实现的呢? 要我讲解 gcc 源码我还没有这个能力, 不过 gcc 有个功能可以让我们比较方便地窥探出其中的技术内幕
$ gcc 源文件 -S -o 目标文件
这样源文件会被编译成汇编指令, 接着分析汇编指令好了.
    上面那个例子情况比较复杂, 不妨分析下面这段简短的代码
void func(int a, int* b, int c)
{
    int nested(void)
    {
        return a + c;
    }
    *b = nested();
}
    这段代码在我机器上 (环境: x86 / Ubuntu 9.10 / GCC 4.4.1) 编译得到的汇编代码如下

Permanent Link: ?p=82 Load full text

Post tags:

 Assembly
 GCC
 for_each
 Nested Function

STL 悲剧之 for_each

    这篇文章谈一个我使用 STL 中 for_each 的负面心得. 我对这个东西在当前 C++ 语法下约束是否能广泛使用持有怀疑态度, 至于能否替代所有的 for 循环, 我持完全否定观点! 如果您发现文中提及的这档子事情本质上是我不了解 STL 而自寻死路, 请不吝赐教, 在下方回复, 在下感激不尽.

    在 Google 中输入 "for_each" 然后猛击 "I’m feeling lucky", 这时我幸运地跳转到大名鼎鼎的 sgi 的网站里, 页面中有个例子
template<class T> struct print : public unary_function<T, void>
{
    print(ostream& out) : os(out), count(0) {}
    void operator() (T x) { os << x << ' '; ++count; }
    ostream& os;
    int count;
};

int main()
{
    int A[] = {1, 4, 2, 8, 5, 7};
    const int N = sizeof(A) / sizeof(int);

    print<int> P = for_each(A, A + N, print<int>(cout));
    cout << endl << P.count << " objects printed." << endl;
}
    从泛型党的视角看来, 这例子非常不错, 完备而规范地演示了 for_each 的用法. 从此泛型党成了大赢家, 除了 for_each 内部, 外面的代码再也不需要 for 这个关键字了.
    而那些说写 C++ 代码像谈话一样简短, 基本上泛型编程完全不及格的程序员, 在写上面功能的时候一般会这样做
int main()
{
    int A[] = {1, 4, 2, 8, 5, 7};
    const int N = sizeof(A) / sizeof(int);

    int i;
    for (i = 0; i < N; ++i)
        cout << A[i] << ' ';
    cout << endl << i << " objects printed." << endl;
}
    在喷这个改编版的丑陋之前, 不妨先淡定下来, 想想这个例子到底想做什么, 无非是给个数组 (或者其它什么容器) 把每个成员输出 (或者做个什么其它简单的操作), 并记录被操作的元素个数. 然而, 完成这个操作的代码量与它的功能相比, 臃肿得令人发指. 所以, 在用了几次 for_each 之后, 我完全丧失了当初的激情, 变得极其懒惰, 不愿意把简单的操作抽取到函数外部构造成一个对象然后传递给 for_each; 在维护自己以前写的代码时, 也会偷偷把 for_each 改回 for 循环, 因为有的时候我实在不知道伙伴们把我弄出来的那个函数对象类型给维护到哪里去了.
    当然, 我并不认为这是 for_each, 或者 STL, 或者泛型的错. 在我心目中, 泛型思想是风骚的, for_each 的设计是超前的, 问题出在 C++ 语法这个悲剧帝身上, 或者说, 硬要用 C++ 这个传统的面向过程加面向对象语言的语法来包装对象, 来实现类似高阶函数这种函数式编程中的特性, 是悲剧的根源所在. 不仅是 for_each, 连 find_if 等类似的 STL 函数都差不多一样的下场, 与其构造一个精妙绝伦的函数对象类型, 程序员还不如老老实实写个破循环来得实在.
    一个振奋人心的消息是, C++ 新标准即将发布了, 它会支持闭包对象, 那样的话, for_each 以及其它函数就不会像现在这么处境尴尬了.

Permanent Link: ?p=81 Load full text

Post tags:

 C++
 STL
 for_each


. Back to Bit Focus
NijiPress - Copyright (C) Neuron Teckid @ Bit Focus
About this site