存档

文章标签 ‘编译’

杯具带来的教训-关于C和C++代码的混合链接

2012年8月29日 sigma 4 条评论 199,674 views

====我是分割线,只是想说明下面的都是废话,大家可以直接跳到下一个分割线====

这几天需要把Parsec(普林斯顿出品的一组多程序程序benchmark,C++编写,貌似是鼎鼎有名的李凯教授带的组出的)的一些程序移植到某模拟器上,由于模拟器没有OS,很多系统调用都是用硬件模拟。因此,parsec的pthread库需要替换成硬件提供的系统调用。在该模拟器上,之前已经有用汇编代码写好的系统调用库以及编程成了相应的静态链接库 .a文件。因此,理论上,只需要换掉pthread头文件,换成.a对应的头文件,并把对应的pthread函数换成硬件模拟的系统调用函数即可。

花了好大力气把所有的头文件以及pthread函数换掉,并且把Makefile的编译器换成交叉编译器,LDFLAGS里的多线程库换成了模拟器系统调用的.a库(通过 -Ldir -llib 替换,其中dir是.a文件所在目录,lib是.a文件的文件名去掉前缀lib和后缀.a的剩余部分)。一切就绪,敲下make,看着一个个.cpp变成了.o,我心里暗喜。可是,当所有.cpp变成.o后,杯具出现了,屏幕刷出一堆“undefined reference to xxxx”,其中xxx就是模拟器系统调用函数名。

奇了怪了,怎么会找不到reference呢,我第一感觉是LDFLAGS写错了,可是检查了几遍,发现没错。并且用同样的LDFLAGS链接splash2中的FFT程序都可以。因此,不太可能试着问题,并且为了确认,我加了-v选项,把链接的细节打出来了,发现没有任何问题。

排除了一种可能,但是问题并没有解决,我只好放狗搜。搜到的答案都是说由于库的链接顺序,会导致这问题,并且通假如某参数,链接时进行repeat搜索,可以避免该问题(当然,repeat的代码就是降低链接速度)。于是,我遍历了各种影响链接顺序的可能,包括LDFLAGS本身的顺序,以及源代码中包含头文件的顺序,可是,杯具依然。

两天过去了。。。还是一无所获,期间只好跑去机房调另外一个东西。昨天,在走投无路的情况下,狠下决心,用c语言重写parsec的这个benchmark。但是,决心容易下,真写谈何容易,看了半天,才把算法大致看明白。再看代码的过程中,我突然想,要不直接把模拟器的系统调用的汇编码嵌入到源代码中,不用.a形式静态链接,但是,由于这些汇编码的寄存器都是按mips标准命名用的,直接放进去,可能交叉编译器无法识别。于是,只好作罢,这时,突然想,为什么FFT可以,这个不可以,莫非是因为C++和C语言链接的方式不一样。想到这,突然有种豁然开朗的感觉,因为模模糊糊记得C函数的符号表和C++函数的符号表是不一致的。

====杯具终于完了,废话也终于完了,下面进入正题,貌似有点头重脚轻的感觉====

在c语言中,全局函数foo(int)在符号表中是_foo,而c++中,为了支持函数重载,会编译成类似_foo_int的。这就导致了前述xxx的系统调用会编译成_xxx_yyy(yyy是参数类型)的形式存在.o中。难怪链接器会找不到reference。

因此,下一步就是如何让C++代码能链接到c代码中了。于是继续放狗搜“c c++ link”,搜到了伟大的太阳的这篇文章:

Mixing C and C++ Code in the Same Program

这篇文章说道,其实可以通过extern关键词声明函数是c函数还是c++函数。当然,也可以直接extern某个头文件,将整个头文件里的函数都声明是啥函数。比如,下面代码:

extern "C" {
  #include "sb.h"
}

就降sb.h 里面的函数都声明是c函数。这样,在c ++代码中,碰到sb.h中的函数,就会当成c函数来对待。

对于我碰到的问题,可以前述extern 头文件来解决。但是,对于大部分头文件来说,我们并不知道这个头文件里的函数是c函数还是c++函数。这就要求头文件编写者做些工作,一般来说,现在的系统库里面都能看到下面的语句:

#ifdef __cplusplus
extern "C"{
#endif

//some function declaration 

#ifdef __cplusplus
             }
#endif

上面的代码就是为了让库兼容c和c++.因为使用g++编译器的时候,实际上是带了__cplusplus的声明,因此整个头文件的函数都会被额外的声明是c函数。之后g++会特殊处理,从而保证链接不出错。

最后,再说几句废话,博客荒废已久,本来以为八月必然交白卷的,想不到最后还是憋出来一篇。。。

分类: 贝壳 标签: , ,

程序(进程)在内存中的组织-由ld_brk_point以及brk函数谈起

2012年4月9日 sigma 1 条评论 39,105 views

今天调龙芯模拟器,发现里面有个ld_brk_point,一直不知道是啥东西,后来搜了下,发现就是malloc函数申请空间的起始地址。改值可以通过brk()以及sbrk函数设置。为了说清楚这个问题,有必要先了解下Linux下程序运行时(进程)在内存中的组织。

linux Flexible Address Space Layout

linux Flexible Address Space Layout

阅读全文…

程序/进程的前世今生

2012年2月8日 sigma 5 条评论 6,804 views

在这篇文章中,将介绍下程序从源码,到目标文件,到二进制码,再到装载,运行以及退出的整个过程,简称程序/进程的前世今生。

首先,区分一下程序和进程的概念。程序是一个静态的概念,而进程是一个动态的概念。程序一般是指从源码到二进制码这些过程的实体(勉强简称为前世),而进程则是从装载,到执行,到推出的实体(勉强简称为今生)。维基百科对两个概念的定义为:

程序的定义:

计算机程序或者软件程序(通常简称程序)是指一组指示计算机或其他具有讯息处理能力装置每一步动作的指令,通常用某种程序设计语言编写,运行于某种目标体系结构上。

进程的定义:

行程(英语:Process,中国大陆译作进程,台湾译作行程)是计算机中已执行程序的实体。行程本身不会执行,是线程的容器。程式本身只是指令的集合,行程才是程式(那些指令)的真正执行。

对于上面进程的定义,个人觉得有点问题(不仅仅是大陆台湾叫法不同的问题),我认为应该是”进程是计算机已执行程序的实体。对于多线程程序,程序可能存在多个同时执行的指令流和控制流,称为线程。程序本身知识指令的集合,进程才是那些指令的真正执行”。

下面,就开始介绍程序/进程的前世今生,由于内容较多,在这里只是介绍前世今生的各个阶段,各个阶段的详细介绍,后面有空的话,并且觉得有必要的话,会单独写一文。不废话,转入正题,程序/进程大致需要经过以下阶段完成其前世今生(以C语言以及Linux进程为例): 阅读全文…

无觅相关文章插件,快速提升流量