( copy from http://banjuan.net/wiki/trick/c-macro, 欢迎过去围观)

目标

这里,我的目标是使用宏进行代码生成。例如以下宏:
ATTR(id, name, value, price)

需要展开为一组函数调用:
read(“id”, item->id);
read(“name”, item->name);
read(“value”, item->value);
read(“price”, item->price);

而上面的宏是在一个更大的宏中使用:
LOAD_ITEM(MyLoader, \
ATTR(id, name, value, price), \
GROUP(prize, p_id, p_value) )

这个大宏需要展开为一个函数体:
int MyLoader::load_item()
{
read(“id”, item->id);
read(“name”, item->name);
read(“value”, item->value);
read(“price”, item->price);
while( m_xml.findChildElem(“prize”) )
{
struct prize* sub_item = new struct prize;
read_sub(“p_id”, sub_item->p_id);
read_sub(“p_value”, sub_item->p_value);
item->prize.push_back(sub_item);
}
return 0;
}

ATTR(id, name, value, price) 的实现

先说一组函数如何生存吧。这个其实就是个for/while循环,或者理解为递归也行。

可是GCC不支持宏的循环/递归处理。宏的复杂应用一般也就是不定项参数了(通常 是封装printf之类的),可是那也就是将参数直接传递罢了。

从我查阅到的资料来看,所有的递归宏处理都是转化为了【穷举】进行实现的。

穷举 =? 递归

由于宏ATTR()的参数是任意个属性,可以预先设定好参数的最大个数,然后人肉 递归,穷举出所有的展开结果:
#define ATTR_(attr) read(#attr, item->attr)
#define ATTR_0(attr, args…)
#define ATTR_1(attr, args…) ATTR_(attr); ATTR_0(args)
#define ATTR_2(attr, args…) ATTR_(attr); ATTR_1(args)
#define ATTR_3(attr, args…) ATTR_(attr); ATTR_2(args)
#define ATTR_4(attr, args…) ATTR_(attr); ATTR_3(args)
#define ATTR_5(attr, args…) ATTR_(attr); ATTR_4(args)

那么,如果有四个参数,就可以直接调用ATTR_4()这个宏来处理四参数的展开:
ATTR_4(id, name, value, price)
=> ATTR_(id); ATTR_3(name, value, price)
=> ATTR_(id); ATTR_(name); ATTR_2(value, price)
=> ATTR_(id); ATTR_(name); ATTR_(value); ATTR_1(price)
=> ATTR_(id); ATTR_(name); ATTR_(value); ATTR_(price); ATTR_0()
=> ATTR_(id); ATTR_(name); ATTR_(value); ATTR_(price);

可是,如果我有五个参数,咋办???只有乖乖调用ATTR_5(….)这个宏。 这种人工计算参数个数的做法实在是土。有没有办法自动计算参数的个数??

GCC的预处理器并没有提供参数个数的变量,而且貌似网上也很少有人提到需要这个值。 不过,今天查看维基百科上的资料时1),意外的发现 有人实现了一个宏 2) ,用来计算参数的个数:
#define PP_NARG(…) \
PP_NARG_(__VA_ARGS__,PP_RSEQ_N())
#define PP_NARG_(…) \
PP_ARG_N(__VA_ARGS__)
#define PP_ARG_N( \
_1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
_11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
_21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
_31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
_41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
_51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
_61,_62,_63,N,…) N
#define PP_RSEQ_N() \
63,62,61,60, \
59,58,57,56,55,54,53,52,51,50, \
49,48,47,46,45,44,43,42,41,40, \
39,38,37,36,35,34,33,32,31,30, \
29,28,27,26,25,24,23,22,21,20, \
19,18,17,16,15,14,13,12,11,10, \
9,8,7,6,5,4,3,2,1,0

真是精巧呀。使用 PP_NARG() 宏,我们就可以自动将 ATTR() 转换为对应的 带参数个数的穷举版宏了。
#define CONNECT(a, b) CONNECT1(a, b)
#define CONNECT1(a, b) CONNECT2(a, b)
#define CONNECT2(a, b) a##b
#define EXPAND_NARG(name, args…) \
CONNECT(name, PP_NARG(args))(args)

#define ATTR(args…) EXPAND_NARG(ATTR_, args)

以上宏定义整理在文件macro.h中了,读者可以下载回来使用 gcc -E 展开宏, 尝试一番。

LOAD_ITEM(loader, code1, code2, …)

使用上面的宏,已经可以搞定 ATTR() 和 GROUP() 了。至于外面的大宏, 实际上是先展开为一下的代码:
LOAD_ITEM_FUNC(MyLoader)
{
ATTR(id, name, value, price);
GROUP(prize, p_id, p_value);
LOAD_ITEM_END();
}

上面的代码再进一步展开为实际的函数体。

对于这里的展开,我暂时还没发现什么巧妙的做法,唯有使用上述的【穷举法】。 具体就没啥技术含量了,也不再多解释了。

参考

* Roland Illig, Re: __VA_NARG__, http://newsgroups.derkeiler.com/Archive/Comp/comp.std.c/2006-01/msg00072.html
1) 一时找不到来源了,明天补上
2) 这就是今天最大的收获啦~~

, , , ,

python语言里有【装饰器】这么个语法糖。简单地理解就是,它能够神不知鬼不觉地替换掉被装饰的函数。【李代桃僵】之类的形容词当然不是描述装饰器的,而似乎装饰器也很少在代码中使用。不过,最近hack别人的python代码(具体来说,就是hack强大的calibre软件的recipe处理,添加cache功能),发现装饰器的设计本意【处理函数的执行环境】十分的有用。

以django项目中的某个功能为例,网址http://py.banjuan.net/tool/build_recipe/对应django工程中的某个函数 tool.build_recipe(request),这个网址GET的时候吐出网页,而POST的处理结果以json方式返回。那么我们就可以编写两个装饰器来专门处理这种页面渲染和json转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from django.http import HttpResponse
from django.utils import simplejson
from django.shortcuts import render_to_response

def page_render(view):
    def func(request, *arg):
        (tpl, ret) = view(request, *arg)
        return render_to_response(tpl, ret)
    return func

def json_render(view):
    def func(request, *arg):
        pydata = view(request, *arg)
        json = simplejson.dumps(pydata, ensure_ascii=False)
        return HttpResponse(json)
    return func

如果需要装饰,那么就是这样写:

1
2
3
@page_render
def build_recipe(request):
    return "running build_recipe"

给略懂python额外讲解一下,上面的代码定义了两个函数page_render, json_render,函数的返回值还是函数。当这两个*_render(其实就是所谓的装饰器了,和普通函数没啥区别)用来装饰build_recipe(request)时,就会起到【李代桃僵】的左右,实际执行的就只是func(xxxx)了。被装饰的原函数build_recipe(request)会作为参数传递给page_render。形象地理解,上面那句@page_render会等效于:

1
build_recipe = page_render(build_recipe)

实际上,我的代码中是这样写的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def build_recipe(request):
    #@csrf_protect
    @page_render
    def get(request):
        return ("tool/build_recipe.html", {})

    @json_render
    def post(request):
        content = None
        if 'content' in request.POST:
            content = request.POST['content']

        if 'recipe' in request.FILES:
            f = request.FILES['recipe']
            content = f.read()

        ret = 1000
        if content:
            name = mktemp(prefix=u'ebook-', suffix='.recipe')
            with open(name, 'wb') as f:
                f.write(content)
                f.close()
            cmd = EBOOK_CONVERT%(name, name+u'.mobi')
            #ret = os.system(cmd)
            ret = cmd
        return {'ret': ret}

    if request.method == 'POST':
        return post(request)
    else:
        return get(request)

根据request.method来分流处理,get()/post()只是返回基本数据,然后交由各自的装饰器进行数据渲染(弄成网页,变为json等)。于是上面的page_render/json_render便可以大量应用与其他类型的函数上,节省代码。

而在实际项目中,除了页面渲染,一个网页请求可能需要先验证登陆态、然后校验典型参数的有效性,为了调试还得统一捕抓异常,为了对付各种黑客还需要csrf_token(就是上面那行注释掉的装饰器),返回结果也还要统一,等等。这些公共的操作都是可以使用装饰器来实现。根据不同的页面权限,组合使用不同的装饰器(会从上倒下层层装饰),达到简化工作的目的。

当然,装饰器的另外一个作用技巧是用来实现注册功能,能够【显得】简约、整洁许多:

1
2
3
4
5
6
7
8
9
10
11
12
funcs = {}
def register(func):
    funcs[func.func_name] = func
    return func

@register
def abc(msg): print msg

@register
def show(): print "hello"

print len(funcs), " functions registed"

最后,果然觉得python的诡异细节真是无穷尽啊。


啊,补充说一下。最初想要用装饰器,是因为calibre的处理recipre的代码中,下载url的start_fetch(url)被调用得太过凌乱了,手动修改各处调用来增加cache会很崩溃的。于是就想到了【李代桃僵】的装饰器的特性,虽然其他地方仍然是调用start_fetch(url),但是已经被我神不知鬼不觉地掉包了,HACK成功!或许有人会想,为啥不直接修改start_fetch(url)的内部代码??介个嘛,这要是因为start_fetch(url)内处的处理也很冗长凌乱,改起来也很崩溃……所以,还是【装饰器】好用啊!

, , ,

以前也尝试过使用nginx代替apache,不过折腾了许久,竟然搞不定各种奇奇怪怪的配置,于是不了了之。昨晚深感VPS被内存大户apache耗用得太厉害了,于是启动切换至nginx的计划。(话说,前几天看到了nginx发布了1.0.0正式版,也算是支持一下 :)

手动编译安装。默认带的配置nginx.conf有简约的例子。照着做。不懂的问谷歌。

。。。可是。。。然后。。。我竟然搞不定配置。。。恨。。。

说不出哪里不对,折腾了一晚上。郁郁地睡去。

今天早上起床,不再看各种博客、教程、谷歌了,直奔nginx.org看英文wiki!于是,发现了第一个趣事

Nginx works perfectly well with a wide variety of applications, and WordPress is certainly one of them. NginX’s configuration language is very powerful and straightforward if one is familiar with it, but often people coming from other servers are not sure how things work in NginX and just copy and paste whatever they see from a blog that seems to fill their needs. Everyone, especially people new to NginX, should check out the nginx.org documentation for an overview of how things work and should be done in NginX.

加粗部分即为亮点。大意是说,nginx的配置是很牛X的,但以前使用其他HTTP服务器的人常常不知道nginx的配置咋整,然后就从网上随便找个博客改改别人的配置就贴过来了。。。天啊!这不就是在说我么!!多次经验证明,拷贝配置是最纳闷最悲剧的事了。。。所以,大家要多去看nginx的文档啊!

然后我就虚心地阅读文档。嗯,原来nginx.conf里面的配置段是层层继承的,server{}里的root配置会继承给location{},所以那些每个location{}里都定义root的人肯定都是折翼的天使了。不过,Reference location的说明中,对某个有趣的特性讲述得太简陋了,倒是在try_files的说明里对location的@特性有个很详细的举例,我这里做一个类比:

# @xxx 定义一个名称
location @dabr {
  rewrite /([a-z]*)/(.*)$ /$1/index.php?q=$2&$arg;
}
location /netputer/ {
  try_files $uri $uri/ @dabr;
}
location /mytwitterapi/ {
  try_files $uri $uri/ @dabr;
}

我的理解就是,@abc能够用来给try_files之类的命令提供一个增强处理的机会,进行if{}判断啊,rewrite一下啊什么的。很少在其他博客中看到对这个特性的介绍,发现它觉得有些小惊喜。嘿嘿

另外,在第一篇入门指引上还发现了一个对php.ini的配置的安全提示:

As said previously, Nginx does not care about files but rather locations. This location block matches a URI that ends in .php but it does not care if it’s a file or not. Therefore a request for /forum/avatars/user2.jpg/index.php will be matched and sent to PHP, and if PHP is not configured properly PHP will then execute /forum/avatars/user2.jpg when /forum/avatars/user2.jpg/index.php doesn’t exist. This provides a huge security risk. Do note that this is not a bug in Nginx, it’s the intended behaviour and as such will not be “fixed”.
This can also be fixed on the PHP side by setting cgi.fix_pathinfo=0 in the php.ini file.

我了个去!再次说明网上教程什么的真是不靠谱啊!!例如我搜索【nginx php debian】所看到的这篇教程,里面就特别说明了要把fix_pathinfo设置为1(默认是0的)。。。而我还傻傻地信了。。。

最后,就是照着wiki,参考Reference来配置好了nginx。由于是手动编译nginx的,所有/etc/init.d/里没有nginx的启动脚本。不过尝试执行nginx -h,却发现它的命令行参数及其简约。其中有个参数-s signal基本上类似于一个控制器了,能够让nginx进程重新读配置、退出等:

-s signal : send signal to a master process: stop, quit, reopen, reload

嗯,所以觉得nginx还是很好很强大的嘛。。。

PS: blogilo程序还是不够靠谱。发表完后总是还要到wordpress后台进行二次排版。囧。

,