编译原理学习笔记06_antlr初步

在C语言中模拟含有默认参数的函数

nonoob posted @ Tue, 18 Dec 2012 23:49:24 +0800 in Programming , 10439 readers

【好久没写了,来篇技术科普文】

写C++代码的时候总想当然以为C中也有含默认参数的函数这种玩意儿(值得注意的是Java也不支持C#支持,Scala这种奇葩支持是不足为奇的),然后在编译下面这段代码之后颜面扫尽TwT

#include "default_args.h"
void printString(const char* msg,int size,int style){
    printf("%s %d %d\n",msg,size,style);
}
int main(){
    printString("hello");
    printString("hello",12);
    printString("hello",12,bold);
}
#include<stdio.h>
enum{
    plain=0,italic=1,bold=2
};
void printString(const char* msg,int size=18,int style=italic);
nonoob@nonoobPC$  clang default_args.c -o default_args
In file included from default_args.c:1:
./default_args.h:12:42: error: C does not support default arguments
...

clang果然是人性化的编译器,还会告诉我们真实的原因;不像gcc只会报出一堆慕名奇妙的error信息,读者不妨自己尝试一下,这里就不吐槽了。至于如果我们的目的在于只要编译通过的话,那完全可以无节操地把这段代码当成C++代码,然后用clang++或g++来搞定这一切;最多只是会报出一个warning(而如果把default_args.c换成default_args.cpp的话连clang++都不报任何警告):

clang: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated

传说中程序员只关心error而不管warning,那大可就此打住并到stackoverflow的这个thread上灌水一番。不过如果是那种闲着无聊且非常执着的话(或者是那种没法用C++而只能选择C的情况,抑或是那种考虑到在其他C的源文件中用到printString()函数的情况),那不妨往下看。一个很容易想到的解决方案自然是重载函数了(先不管效率)。在default_args.c删掉函数中的默认值并添加下面这段:

void printString(const char *msg,int size){
    printString(msg,size,italic);
}

但却是:

nonoob@nonoobPC$  clang override_args.c -o override_args
override_args.c:14:6: error: conflicting types for 'printString'

又一次颜面扫尽,C原来连重载函数都不支持>_<,弱爆了。没辙了吗?就不能猥琐地模拟一下然后让C语言程序员也享受一下默认参数的快感吗?macro!C程序员的必杀技,一个被C++程序员吐槽无数的招数:-(,但却是一个很优雅的解决方案^_^

#include<stdio.h>

enum{
    plain=0,italic=1,bold=2
};

void printString(const char* message, int size, int style) {
    printf("%s %d %d\n",message,size,style);
}

#define PRINT_STRING_1_ARGS(message)              printString(message, 18, italic)
#define PRINT_STRING_2_ARGS(message, size)        printString(message, size, italic)
#define PRINT_STRING_3_ARGS(message, size, style) printString(message, size, style)

#define GET_4TH_ARG(arg1, arg2, arg3, arg4, ...) arg4
#define PRINT_STRING_MACRO_CHOOSER(...) \
    GET_4TH_ARG(__VA_ARGS__, PRINT_STRING_3_ARGS, \
                PRINT_STRING_2_ARGS, PRINT_STRING_1_ARGS, )

#define PRINT_STRING(...) PRINT_STRING_MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)

int main(int argc, char * const argv[]) {
    PRINT_STRING("Hello, World!");
    PRINT_STRING("Hello, World!", 12);
    PRINT_STRING("Hello, World!", 12, bold);
    return 0;
}

看到这么一坨代码,估计没几个人喜欢——宏这种对把源代码看成白盒的程序员实在不友好的东西,然而却让所有的C程序员大受其益。不妨看一下NULL的定义:

#define NULL ((void *)0)

闲话不扯了,看看前段代码是怎么回事;毕竟子曾曰过:举一隅不以三隅反,则不复也。

macro本身也不是什么见不得人的东西,说到底就是方便程序员偷懒的,在实现的最终目的上和函数没有本质区别。这里需要注意的是__VA_ARGS__这个东东。其洋名叫Variadic Macros,就是可变参数宏,在这里是配合“...”一起用的。可变参函数想必C程序员都不陌生,就是没吃过猪肉也见过猪跑是吧,比如printf;这里也有个tutorial。我们在这里需要知道的是宏定义(define)处的“...”是可以和宏使用(use)处的多个参数一起匹配的。下面以PRINT_STRING("Hello, World!", 18);为例说明是怎么展开的。

首先"Hello,World!", 12匹配PRINT_STRING_MACRO_CHOOSER(...)中的"...",于是被扩展成:

PRINT_STRING_MACRO_CHOOSER("Hello, World!", 12)("Hello, World!", 12);

PRINT_STRING_MACRO_CHOOSER("Hello, World!",12)又被扩展成

GET_4TH_ARG("Hello, World!", 12, PRINT_STRING_3_ARGS, PRINT_STRING_2_ARGS, PRINT_STRING_1_ARGS, )

所以整条语句被扩展成了

GET_4TH_ARG("Hello, World!", 12, PRINT_STRING_3_ARGS, PRINT_STRING_2_ARGS, PRINT_STRING_1_ARGS, )("Hello, World!", 12);

接下来看到的是匹配#define GET_4TH_ARG(arg1,arg2,arg3,arg4, ...)arg4的情况,"Hello,World!"匹配args1,12匹配arg2PRINT_STRING_3_ARGS匹配arg3PRINT_STRING_2_ARGS匹配arg4,而其余, PRINT_STRING_1_ARGS, 的部分匹配了“...”,所以经过这一番扩展变成了

PRINT_STRING_2_ARGS("Hello, World!", 12);

即为

printString("Hello, World!", 12,1);

这样一番折腾终于见到庐山真面目了。当然我们可以用gnu cpp查看一下预处理的结果是不是这样的(一般来讲C和C++用preprocessor是一样的)。

...
int main(int argc, char * const argv[]) {
    printString("Hello, World!", 18, italic);
    printString("Hello, World!", 12, italic);
    printString("Hello, World!", 12, bold);
    return 0;
}

这也解释了为什么说用macro的解决方案是优雅的。不妨再看看生成的llvm的ir形式:

nonoob@nonoobPC$  clang macro.c -S -o - -emit-llvm
; ModuleID = 'macro.c'
target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128"
target triple = "i386-pc-linux-gnu"

@.str = private unnamed_addr constant [10 x i8] c"%s %d %d\0A\00", align 1
@.str1 = private unnamed_addr constant [14 x i8] c"Hello, World!\00", align 1

define void @printString(i8* %message, i32 %size, i32 %style) nounwind {
  %1 = alloca i8*, align 4
  %2 = alloca i32, align 4
  %3 = alloca i32, align 4
  store i8* %message, i8** %1, align 4
  store i32 %size, i32* %2, align 4
  store i32 %style, i32* %3, align 4
  %4 = load i8** %1, align 4
  %5 = load i32* %2, align 4
  %6 = load i32* %3, align 4
  %7 = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([10 x i8]* @.str, i32 0, i32 0), i8* %4, i32 %5, i32 %6)
  ret void
}

declare i32 @printf(i8*, ...)

define i32 @main(i32 %argc, i8** %argv) nounwind {
  %1 = alloca i32, align 4
  %2 = alloca i32, align 4
  %3 = alloca i8**, align 4
  store i32 0, i32* %1
  store i32 %argc, i32* %2, align 4
  store i8** %argv, i8*** %3, align 4
  call void @printString(i8* getelementptr inbounds ([14 x i8]* @.str1, i32 0, i32 0), i32 18, i32 1)
  call void @printString(i8* getelementptr inbounds ([14 x i8]* @.str1, i32 0, i32 0), i32 12, i32 1)
  call void @printString(i8* getelementptr inbounds ([14 x i8]* @.str1, i32 0, i32 0), i32 12, i32 2)
  ret i32 0
}

很清爽的代码,令人心旷神怡吧。

废了这么大的力气才做了这么点事,还不如不用“默认参数”呢是吧?但是当把这个写成库的时候,或者以后要经常使用的话这就方便多了,且不容易出错!

为了无聊起见,再看看default_org.h+default_org.c用clang++/g++编译得到的llvm的ir:

nonoob@nonoobPC$  clang++ default_args.c -S -o - -emit-llvm
clang: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated
; ModuleID = 'default_args.c'
target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128"
target triple = "i386-pc-linux-gnu"

@.str = private unnamed_addr constant [10 x i8] c"%s %d %d\0A\00", align 1
@.str1 = private unnamed_addr constant [6 x i8] c"hello\00", align 1

define void @_Z11printStringPKcii(i8* %msg, i32 %size, i32 %style) {
  %1 = alloca i8*, align 4
  %2 = alloca i32, align 4
  %3 = alloca i32, align 4
  store i8* %msg, i8** %1, align 4
  store i32 %size, i32* %2, align 4
  store i32 %style, i32* %3, align 4
  %4 = load i8** %1, align 4
  %5 = load i32* %2, align 4
  %6 = load i32* %3, align 4
  %7 = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([10 x i8]* @.str, i32 0, i32 0), i8* %4, i32 %5, i32 %6)
  ret void
}

declare i32 @printf(i8*, ...)

define i32 @main() {
  %1 = alloca i32, align 4
  store i32 0, i32* %1
  call void @_Z11printStringPKcii(i8* getelementptr inbounds ([6 x i8]* @.str1, i32 0, i32 0), i32 18, i32 1)
  call void @_Z11printStringPKcii(i8* getelementptr inbounds ([6 x i8]* @.str1, i32 0, i32 0), i32 12, i32 1)
  call void @_Z11printStringPKcii(i8* getelementptr inbounds ([6 x i8]* @.str1, i32 0, i32 0), i32 12, i32 2)
  %2 = load i32* %1
  ret i32 %2
}

从这里的IR中我们至少可以得到两点信息:

  • C++编译得到的函数名和C编译得到的不一样(事实上是很不一样,可以参见name mangling),使用c++filt之后我们可以看到C++中的printString的签名实际上是void @printString(char const*, int, int)(i8* %msg, i32 %size, i32 %style)而不再是void @printString(i8* %message, i32 %size, i32 %style)。同时这也解释了为何在C中不会有函数的(静态)重载(没有OO自然动态重载更无从说起)——假设C有函数重载的话,会生成三个同名的函数,而C中调用函数时仅仅根据符号表中的函数名,这样就会造成混乱。【TODO:动态重载实现机理】
  • 编译得到的代码中是看不到任何默认构造函数的信息的(同样连enum的信息也没有了),3条call指令中我们得到的只不过是对应下面源代码的指令(也没有生成三个签名不同但名字相同的函数printString())。
​printString("hello",18,1);
printString("hello",12,1);
printString("hello",12,2);

到此为止,正文结束。下面贴捣鼓的一段含宏的代码~~

#include<stdio.h>
#include<stdarg.h>

#define LOGSTRING(fm,...) printf(fm,__VA_ARGS__)
#define MY_DEBUG(format,...) fprintf(stderr,NEWLINE(format),##__VA_ARGS__);
#define NEWLINE(str) str "\n"
#define GCC_DBG(format,args...) fprintf(stderr,format,##args)
#define DEBUG(args) (printf("DEBUG: "), printf args)
#define STRING(str) #str
#define NULL 3

int main(int argc,char**argv){
    LOGSTRING("Hello %s %s\n","Hong""xu","Chen");
    MY_DEBUG("my debug")
    GCC_DBG("gcc dbg\n");
    int n = 0;
    if (n != NULL) DEBUG(("n is %d\n", n));
    puts(STRING(It really Compiles!));
    return 0;
}

 

参考资料:

  1. http://stackoverflow.com/questions/3046889/optional-parameters-with-c-macros
  2. http://www.cnblogs.com/alexshi/archive/2012/03/09/2388453.html
Avatar_small
Garfileo said:
Wed, 19 Dec 2012 08:37:15 +0800

经常 foo_bar_[1,2,3]_args (); 的人路过 :)

Avatar_small
nonoob said:
Wed, 19 Dec 2012 09:35:11 +0800

@Garfileo: 啥语言下的神马用法?

Avatar_small
Garfileo said:
Wed, 19 Dec 2012 14:12:53 +0800

:) C 语言的人文用法

Avatar_small
nonoob said:
Wed, 19 Dec 2012 16:18:10 +0800

@Garfileo: 求解怎么写的?

Avatar_small
Garfileo said:
Wed, 19 Dec 2012 23:49:39 +0800

:( 居然木看出来我在开玩笑。

因为我经常遇到这个问题,一直都是像 PRINT_STRING_2_ARGS 这样处理的。

Avatar_small
nonoob said:
Thu, 20 Dec 2012 09:25:59 +0800

@Garfileo: =_=|||
我还是前不久才知道有这么奇葩的用法...

AAA said:
Sun, 29 May 2022 04:09:53 +0800 I’d should check with you here. Which isn’t something It’s my job to do! I enjoy reading a post that should get people to believe. Also, thank you permitting me to comment! 麥克風
Linker SEO said:
Wed, 03 Jan 2024 01:36:06 +0800

Slots that break easily can ruin the gaming experience. เว็บตรง แตกง่าย

Linker SEO said:
Thu, 04 Jan 2024 14:21:00 +0800

The sheer size and selection of these web slots are mind-blowing. Great job. สล็อตค่ายใหญ่

Linker SEO said:
Mon, 15 Jan 2024 14:15:14 +0800

It is fine, in any case assess the data and actualities around this right. รวมสล็อตทุกค่ายในเว็บเดียว

jsimitseo said:
Tue, 16 Jan 2024 18:00:44 +0800

Decent data, profitable and fantastic plan, as offer well done with smart thoughts and ideas, loads of extraordinary data and motivation, both of which I require, on account of offer such a supportive data here.  เกมส์ไพ่ป๊อกเด้ง

jsimitseo said:
Wed, 17 Jan 2024 01:17:42 +0800

Decent data, profitable and phenomenal outline, as offer well done with smart thoughts and ideas, bunches of extraordinary data and motivation, both of which I require, on account of offer such an accommodating data here.  เว็บสล็อตโรม่า

williamseo said:
Sun, 28 Jan 2024 00:25:37 +0800

I stumbled upon Timely Magazine News while searching for a reliable source of up-to-the-minute information, and I must say, it has become my go-to platform for staying informed. The commitment to delivering timely and relevant news is evident in every aspect of this magazine. The range of topics covered is impressive, catering to a diverse audience with varied interests. timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews timelymagazinenews

Linker SEO said:
Wed, 31 Jan 2024 17:42:52 +0800

Embark on a journey of creativity and craftsmanship at Chennai Fashion Institute, the premier embroidery school in the region. Explore the rich heritage of embroidery techniques unique to Chennai. Our experienced faculty and state-of-the-art facilities ensure a transformative learning experience. Elevate your skills and join the legacy of Chennai's embroidery tradition. Chennai embroidery school

Linker SEO said:
Fri, 02 Feb 2024 22:11:56 +0800

Optimize your website's performance by resizing your images intelligently. resize image

jsimitseo said:
Fri, 02 Feb 2024 23:52:41 +0800

For genuine devotees of this string I will address is a free on the web!  concierge doctor naples

Linker SEO said:
Sat, 10 Feb 2024 14:55:55 +0800

se abbreviations and symbols to increase speed and accuracy. Be selective about facts and ideas and write down only what’s essential. Deciding what to keep requires some practice, so be patient. AI Productivity Tips


Login *


loading captcha image...
(type the code from the image)
or Ctrl+Enter