C语言陷阱II

相信以后还会有III,IIII,V……

类型转换是有代价的

最近在写虚拟机解释引擎的时候遇到的这个问题。Java虚拟机中虽然有多种数据类型,但是实际上在Java栈上,只有两种类型,占用一个栈槽位的和占用两个栈槽位的,也就是4字节与8字节。所以一开始设计Java栈的API的时候使用了两套接口:

unsinged int Stack_pop4(T);
void Stack_push4(T, unsinged int);
unsigned long long Stack_pop8(T);
void Stack_push8(T, unsinged long long);

这里的问题是,将float类型push之后,当再次pop出来时,结果就不正确了。

C语言中float占4个字节,无符号整形数也是4个字节,这样如果有4个字节的数据放在内存中,具体它是什么值取决于我们把它当成什么类型去解释。这一点非常类似数据与结构的关系,数据往往只有一份,但是可以用多种结构来表示,结构只是数据的不同视图罢了。内存中的数据当然也是这样,放在那里都是没有类型的字节,就看CPU如何去解释它们。

但是将同样的数据用不同的类型来解释,在C语言中不能用类型转换来实现,原因在于类型转换不是免费的!

loat x = 3.14;
unsigned int y = (unsigned int)x;
float z = (float)y;  // z已经不是3.14了

之所以说类型转换不是免费的,因为这里可以将它理解为一种运算:unsigned int trans_f2i(float).并且这种运算并不是拿着原始数据生成一个新的数据的视图,而是复制一份数据并对数据做修改。显然y与x在内存中的地址是不一样的。

使用指针可以将任意合法内存当做任意类型的读写。

float x = 3.14;
unsigned int *p = &x;
unsigned int y = *p;
float *f = &y;
float z = *f;  // z = 3.14

灵活使用union也可以获得同样的效果。

union {
       unsigned int u;
       float f;
}u;
float x = 3.14;
u.f = x;
unsigned int y = u.u;
float z = u.f; // z = 3.14

将任意合法内存作为参数传给已定义的函数

需求看上去有点奇怪,不过如果真的写起Runtime类的程序,这种需求恐怕还是挺多的。可以理解为函数都已经定义好了,但是函数的参数可能是通过网络传来的,或者通过其他过程计算得到的一块内存区域。比如通过dlopen,dlsym可以找到函数指针,不过如何用一个通用模块给找到的函数传递参数呢?

合理的利用程序调用栈的布局似乎是一个好主意。这篇文章里面详细介绍了栈相关的细节(貌似链接挂了,以后我会自己写一篇放上)。比如如下这段程序:

int foo(int x, int y, int z);
struct Args {
   unsigned data[MAX];
};
int main()
{
   int(*f)();
   f = foo;
   struct Args args;
   int *body = (int*)args.data;
   body[0] = 1;
   body[1] = 2;
   body[2] = 3;
   f(args);
}

利用结构体值传递的特点,可以将一块内存作为参数传给接受任意个任意类型参数的函数。当然这里的方法很多,用不定长数组总是分配在栈的低地址的特点也可以达到同样的效果,就不在举例子了。

让我掉进陷阱的是这套方案在64bit的系统上不能用了……最后发现其实就是ABI的问题。在64bit上gcc应该是默认用寄存器传参,而32bit都是默认用栈传参。看到也有人有同样的需求x86-64-forcing-gcc-to-pass-arguments-on-the-stack。我没有查相关的ABI文档,不过做了点实验,反编译了一些代码,64bit上应该前6个参数都是通过寄存器传递的,多于6个的参数通过栈传递。当然这个不是很准确,权威答案还需要查看相应的文档。我只是用这个例子测了一下:

struct args {
  long long data[64];
};

int foo(int x1, int x2, int x3
                , int x4, int x5, int x6
            , int x7, int x8, int x9){
  printf("hello, world %d %d %d\n", x7, x8, x9);

  return 1;
}

typedef int(*fTy)();
int main()
{
  fTy f;
  f = foo;

  struct args Args;
  Args.data[0] = 100;
  Args.data[1] = 200;
  Args.data[2] = 300;
  f(1,2,3,4,5,6,Args);  // print hello, world 100 200 300
  return 0;
}

当然这种方法本身就是一种hack,是破坏ABI的一种做法。即使在32bit上也是不能保证正确性的,因为32bit上可以通过优化利用2个寄存器传参,只是默认关闭而已。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容