2004年06月24日

为什么每个人都急不可耐?

走进任何一家书店,你会看见《Teach Yourself Java in 7 Days》(7天Java无师自通)的旁边是一长排看不到尽头的类似书籍,它们要教会你Visual Basic、Windows、Internet等等,而只需要几天甚至几小时。我在Amazon.com上进行了如下搜索:
    pubdate: after 1992 and title: days and (title: learn or title: teach yourself)
    (出版日期:1992年后 and 书名:天 and (书名:学会 or 书名:无师自通))
我一共得到了248个搜索结果。前面的78个是计算机书籍(第79个是《Learn Bengali in 30 days》,30天学会孟加拉语)。我把关键词“days”换成“hours”,得到了非常相似的结果:这次有253本书,头77本是计算机书籍,第78本是《Teach Yourself Grammar and Style in 24 Hours》(24小时学会文法和文体)。头200本书中,有96%是计算机书籍。
结论是,要么是人们非常急于学会计算机,要么就是不知道为什么计算机惊人地简单,比任何东西都容易学会。没有一本书是要在几天里教会人们欣赏贝多芬或者量子物理学,甚至怎样给狗打扮。
让我们来分析一下像《Learn Pascal in Three Days》(3天学会Pascal)这样的题目到底是什么意思:

 

学会:在3天时间里,你不够时间写一些有意义的程序,并从它们的失败与成功中学习。你不够时间跟一些有经验的程序员一起工作,你不会知道在那样的环境中是什么滋味。简而言之,没有足够的时间让你学到很多东西。所以这些书谈论的只是表面上的精通,而非深入的理解。如Alexander Pope(英国诗人、作家,1688-1744)所言,一知半解是危险的(a little learning is a dangerous thing)

Pascal:在3天时间里你可以学会Pascal的语法(如果你已经会一门类似的语言),但你无法学到多少如何运用这些语法。简而言之,如果你是,比如说一个Basic程序员,你可以学会用Pascal语法写出Basic风格的程序,但你学不到Pascal真正的优点(和缺点)。那关键在哪里?Alan Perlis(ACM第一任主席,图灵奖得主,1922-1990)曾经说过:“如果一门语言不能影响你对编程的想法,那它就不值得去学”。另一种观点是,有时候你不得不学一点Pascal(更可能是Visual Basic和JavaScript之类)的皮毛,因为你需要接触现有的工具,用来完成特定的任务。但此时你不是在学习如何编程,你是在学习如何完成任务。

3天:不幸的是,这是不够的,正如下一节所言。

 

 

10年编程无师自通

一些研究者(Hayes、Bloom)的研究表明,在许多领域,都需要大约10 年时间才能培养出专业技能,包括国际象棋、作曲、绘画、钢琴、游泳、网球,以及神经心理学和拓扑学的研究。似乎并不存在真正的捷径:即使是莫扎特,他4 岁就显露出音乐天才,在他写出世界级的音乐之前仍然用了超过13年时间。再看另一种音乐类型的披头士,他们似乎是在1964年的Ed Sullivan节目中突然冒头的。但其实他们从1957年就开始表演了,即使他们很早就显示出了巨大的吸引力,他们第一次真正的成功——Sgt. Peppers——也要到1967年才发行。Samuel Johnson(英国诗人)认为10 年还是不够的:“任何领域的卓越成就都只能通过一生的努力来获得;稍低一点的代价也换不来。”(Excellence in any department can be attained only by the labor of a lifetime; it is not to be purchased at a lesser price.) 乔叟(Chaucer,英国诗人,1340-1400)也抱怨说:“生命如此短暂,掌握技艺却要如此长久。”(the lyf so short, the craft so long to lerne.)
下面是我在编程这个行当里获得成功的处方:

 

对编程感兴趣,因为乐趣而去编程。确定始终都能保持足够的乐趣,以致你能够将10年时间投入其中。

跟其他程序员交谈;阅读其他程序。这比任何书籍或训练课程都更重要。

编程。最好的学习是从实践中学习。用更加技术性的语言来讲,“个体在特定领域最高水平的表现不是作为长期的经验的结果而自动获得的,但即使是非常富有经验的个体也可以通过刻意的努力而提高其表现水平。”(p. 366),而且“最有效的学习要求为特定个体制定适当难度的任务,有意义的反馈,以及重复及改正错误的机会。”(p. 20-21)《Cognition in Practice: Mind, Mathematics, and Culture in Everyday Life》(在实践中认知:心智、数学和日常生活的文化)是关于这个观点的一本有趣的参考书。

如果你愿意,在大学里花上4年时间(或者再花几年读研究生)。这能让你获得一些工作的入门资格,还能让你对此领域有更深入的理解,但如果你不喜欢进学校,(作出一点牺牲)你在工作中也同样能获得类似的经验。在任何情况下,单从书本上学习都是不够的。“计算机科学的教育不会让任何人成为内行的程序员,正如研究画笔和颜料不会让任何人成为内行的画家”, Eric Raymond,《The New Hacker’s Dictionary》(新黑客字典)的作者如是说。我曾经雇用过的最优秀的程序员之一仅有高中学历;但他创造出了许多伟大的软件,甚至有讨论他本人的新闻组,而且股票期权让他达到我无法企及的富有程度(译注:指Jamie Zawinski,Xemacs和Netscape的作者)。

跟别的程序员一起完成项目。在一些项目中成为最好的程序员;在其他一些项目中当最差的一个。当你是最好的程序员时,你要测试自己领导项目的能力,并通过你的洞见鼓舞其他人。当你是最差的时候,你学习高手们在做些什么,以及他们不喜欢做什么(因为他们让你帮他们做那些事)。

接手别的程序员完成项目。用心理解别人编写的程序。看看在没有最初的程序员在场的时候理解和修改程序需要些什么。想一想怎样设计你的程序才能让别人接手维护你的程序时更容易一些。

学会至少半打编程语言。包括一门支持类抽象(class abstraction)的语言(如Java或C++),一门支持函数抽象(functional abstraction)的语言(如Lisp或ML),一门支持句法抽象(syntactic abstraction)的语言(如Lisp),一门支持说明性规约(declarative specification)的语言(如Prolog或C++模版),一门支持协程(coroutine)的语言(如Icon或Scheme),以及一门支持并行处理(parallelism)的语言(如Sisal)。

记住在“计算机科学”这个词组里包含“计算机”这个词。了解你的计算机执行一条指令要多长时间,从内存中取一个word要多长时间(包括缓存命中和未命中的情况),从磁盘上读取连续的数据要多长时间,定位到磁盘上的新位置又要多长时间。(答案在这里。)

尝试参与到一项语言标准化工作中。可以是ANSI C++委员会,也可以是决定自己团队的编码风格到底采用2个空格的缩进还是4个。不论是哪一种,你都可以学到在这门语言中到底人们喜欢些什么,他们有多喜欢,甚至有可能稍微了解为什么他们会有这样的感觉。

拥有尽快从语言标准化工作中抽身的良好判断力。

抱着这些想法,我很怀疑从书上到底能学到多少东西。在我第一个孩子出生前,我读完了所有“怎样……”的书,却仍然感到自己是个茫无头绪的新手。30个月后,我第二个孩子出生的时候,我重新拿起那些书来复习了吗?不。相反,我依靠我自己的经验,结果比专家写的几千页东西更有用更靠得住。
Fred Brooks在他的短文《No Silver Bullets》(没有银弹)中确立了如何发现杰出的软件设计者的三步规划:

 

尽早系统地识别出最好的设计者群体。

指派一个事业上的导师负责有潜质的对象的发展,小心地帮他保持职业生涯的履历。

让成长中的设计师们有机会互相影响,互相激励。

这实际上是假定了有些人本身就具有成为杰出设计师的必要潜质;要做的只是引导他们前进。Alan Perlis说得更简洁:“每个人都可以被教授如何雕塑;而对米开朗基罗来说,能教给他的倒是怎样能够不去雕塑。杰出的程序员也一样”。
所以尽管去买那些Java书;你很可能会从中找到些用处。但你的生活,或者你作为程序员的真正的专业技术,并不会因此在24小时、24天甚至24个月内发生真正的变化。

 

参考文献

Bloom, Benjamin (ed.) Developing Talent in Young People, Ballantine, 1985.
Brooks, Fred, No Silver Bullets, IEEE Computer, vol. 20, no. 4, 1987, p. 10-19.
Hayes, John R., Complete Problem Solver, Lawrence Erlbaum, 1989.
Lave, Jean, Cognition in Practice: Mind, Mathematics, and Culture in Everyday Life, Cambridge University Press, 1988.

 

答案

各种操作的计时,2001年夏天在一台典型的1GHz PC上完成:
    执行单条指令            1 纳秒 = (1/1,000,000,000) 秒
    从L1缓存中取一个word        2 纳秒
    从主内存中取一个word        10 纳秒
    从连续的磁盘位置中取一个word    200 纳秒
    从新的磁盘位置中取一个word(寻址) 8,000,000纳秒 = 8毫秒

 

脚注

本文的日文译本要感谢Yasushi Murakawa,中文译本要感谢郭晓刚,西班牙文译本要感谢Carlos Rueda,德文译本要感谢Stefan Ram。
T. Capey指出Amazon上面《Complete Problem Solver》的页面中,《Teach Yourself Bengali in 21 days》和《Teach Yourself Grammar and Style》被列在了“购买此书的顾客还买了以下书籍”栏目里面。我猜其中一大部分察看这两本书的人都是从我这里过去的。

文章出处:  http://www.justjsp.com/main/article.php?articleid=1105

H.264 Video Codec Adopted For Next Generation DVD’s

MPEG Developed Video Codec Key to Future High Definition DVDs

Cupertino, CA (June 23, 2004) – Apple? today announced that the DVD Forum has ratified the H.264 Advanced Video Codec (AVC) to be included in the next generation High Definition (HD) DVD format. The H.264/AVC codec was jointly developed by the Moving Picture Experts Group (MPEG) and the International Telecommunication Union (ITU) and has been ratified into the MPEG-4 specification as the next-generation video codec. H.264/AVC is based on open standards and will ship in Apple’s QuickTime? software in an upcoming release next year.

“Apple is firmly behind H.264 because it delivers superb quality digital video and is based on open standards that no single company controls,” said Philip Schiller, Apple’s senior vice president of Worldwide Product Marketing. “QuickTime 6 has already topped 250 million downloads, making it one of the most successful media standards ever, and we will be adding support for H.264 to QuickTime next year.”

H.264/AVC is an extremely scalable video codec, delivering excellent quality across the entire bandwidth spectrum—from high definition television to video conferencing and 3G mobile multimedia. As shown in a preview at the National Association of Broadcasters convention in April, video encoded at full high definition resolution (1920×1080 24p) was played back between 6.8 and 8 Mbps on a dual-processor Power Mac? G5 delivering full HD quality at up to half the data rate of MPEG-2.

As the platform of choice for content creators worldwide, QuickTime delivers the full media experience for thousands of unique software titles, enhanced music CDs and hundreds of digital camera models. More than 250 million copies of QuickTime 6 have been downloaded in less than two years since its release. According to Frost & Sullivan’s 2004 Global Media Streaming Platform Report, between 2002 and 2003 Microsoft’s and Real Networks’ worldwide market share percentages were either stable or declining while QuickTime’s market share increased to 36.8 percent, a close second to Microsoft. Real Networks came in third place with less than 25 percent of the worldwide streaming market share. QuickTime 6.5, which also includes enhanced support for 3rd Generation Partnership Project (3GPP) and 3GPP2 mobile networks, is available as a free download for Mac? and Windows users at www.apple.com/quicktime.

About Apple
Apple ignited the personal computer revolution in the 1970s with the Apple II and reinvented the personal computer in the 1980s with the Macintosh. Apple is committed to bringing the best personal computing experience to students, educators, creative professionals and consumers around the world through its innovative hardware, software and Internet offerings.

2004年06月22日

/**
 * Pixel format. Notes:
 *
 * PIX_FMT_RGBA32 is handled in an endian-specific manner. A RGBA
 * color is put together as:
 *  (A << 24) | (R << 16) | (G << 8) | B
 * This is stored as BGRA on little endian CPU architectures and ARGB on
 * big endian CPUs.
 *
 * When the pixel format is palettized RGB (PIX_FMT_PAL8), the palettized
 * image data is stored in AVFrame.data[0]. The palette is transported in
 * AVFrame.data[1] and, is 1024 bytes long (256 4-byte entries) and is
 * formatted the same as in PIX_FMT_RGBA32 described above (i.e., it is
 * also endian-specific). Note also that the individual RGB palette
 * components stored in AVFrame.data[1] should be in the range 0..255.
 * This is important as many custom PAL8 video codecs that were designed
 * to run on the IBM VGA graphics adapter use 6-bit palette components.
 */

When the framework wants to find an output_format for specific media,it follows the basic logic:firstly find the general output_format by using guess_format(),secondly, add the stream flag to the found output_format,and then just use the shortname as parameter to call guess_format() function!But when it try to locate an input_format,the framework  compare just the short_name of the format with its list manager,So the method should be enhanced in future!

 The video image is separated into one luminance (Y) and two chrominance channels (also called color difference signals Cb and Cr). Blocks of the luminance and chrominance arrays are organized into "macroblocks", which are the basic unit of coding within a picture. Each macroblock is divided into four 8×8 luminance blocks. The number of 8×8 chrominance blocks per macroblock depends on the chrominance format of the source image. For example, in the common 4:2:0 format, there is one chrominance block per macroblock for each of the two chrominance channels, making a total of six blocks per macroblock.

last added on 2005/06/08

2004年06月14日

(转)DirectShow实践经验杂谈(一)

1.当向Filter Graph中加入同一个Filter的多个实例时,使用Intelligent connect,优先使用最晚加入Filter Graph中的那个Filter实例。

2.使用IGraphConfig接口可以将Filter加入Cache,以在Intelligent connect时,提高该Filter的连接优先级。如果要加入Cache的Filter已在Graph中,确信它的所有Pin处于断开状态,而且调用IGraphConfig::AddFilterToCache之后,Graph中的Filter实例会自动Remove掉;当Intelligent connect之后,使用了Cache中的某个Filter实例,则这个Filter实例会被自动加入到Graph,而Cache中这个Filter实例也会被Remove掉。

3.在调试Filter时,每次进入CheckMediaType的Media type,我们在VC中只能看到一串UUID数字,很麻烦!下面的代码能将Media type的描述信息Dump到VC的Output window:
void DisplayMediaType(TCHAR *pDescription,const CMediaType *pmt)
{
// Dump the GUID types and a short description
DbgLog((LOG_TRACE,0,TEXT(“”)));
DbgLog((LOG_TRACE,0,TEXT(“%s”),pDescription));
DbgLog((LOG_TRACE,0,TEXT(“”)));
DbgLog((LOG_TRACE,0,TEXT(“Media Type Description”)));
DbgLog((LOG_TRACE,0,TEXT(“Major type: %s”),GuidNames[*pmt->Type()]));
DbgLog((LOG_TRACE,0,TEXT(“Subtype: %s”),GuidNames[*pmt->Subtype()]));
DbgLog((LOG_TRACE,0,TEXT(“Subtype description: %s”),GetSubtypeName(pmt->Subtype())));
DbgLog((LOG_TRACE,0,TEXT(“Format size: %d”),pmt->cbFormat));
// Dump the generic media types
DbgLog((LOG_TRACE,0,TEXT(“Fixed size sample %d”),pmt->IsFixedSize()));
DbgLog((LOG_TRACE,0,TEXT(“Temporal compression %d”),pmt->IsTemporalCompressed()));
DbgLog((LOG_TRACE,0,TEXT(“Sample size %d”),pmt->GetSampleSize()));
}

4.调试Filter时,想要知道程序是否进入某个函数,可以使用如下的宏定义:
#define DbgFunc(a) DbgLog(( LOG_TRACE \
, 0 \
, TEXT(“CFltTracer(Instance %d)::%s”) \
, mThisInstance \
, TEXT(a) \
));
使用方法为:
CFltTracer::~CFltTracer()
{
// Other cleaning work…
DbgFunc(“~CFltTracer”);
}

5.如下的代码,可以将DirectShow的错误码以文本的方式显示出来:
void ShowError(HRESULT hr)
{
if (FAILED(hr))
{
TCHAR szErr[MAX_ERROR_TEXT_LEN];
DWORD res = AMGetErrorText(hr, szErr, MAX_ERROR_TEXT_LEN);
if (res == 0)
{
wsprintf(szErr, “Unknown Error: 0x%2x”, hr);
}
MessageBox(0, szErr, TEXT(“Error!”), MB_OK | MB_ICONERROR);
}
}

6.0进行DV camcorder编程时要注意,当Filter Graph在运行,而将Camcorder置于Paused状态,”Microsoft DV Camera and VCR”仍然会不停地将Paused时刻的那一帧不断地发送出来(因为DirectShow主要是为Playback设计的,所以这一点对于压缩、合成、写文件的应用非常头疼),送出的Sample时间戳线性递增;而将Camcorder置于Stopped状态,”Microsoft DV Camera and VCR”也就不再送出数据。而Filter Graph上的Pause、Stop操作并不会影响到Camcorder本身的状态。

6.1 Camcorder机器Paused状态持续大约三分钟(测试机为JVC)后,机器的Preview画面会变成蓝屏。此时,即使Filter Graph在运行状态,”Microsoft DV Camera and VCR”也停止输出数据。

7.DirectShow加入Device Filter一般都要靠枚举。Device的名字一般是在枚举的时候,通过IPropertyBag::Read(L”FriendlyName”, &varName, 0)获得。对于DV camcorder,这种方式得到的名字为”Microsoft DV Camera and VCR”;而另一种方法(必须在Windows Me或XP下),通过读取”Description”属性,可以更详细地得到Camcorder的生产厂商名字。参考代码如下:
VARIANT varName;
VariantInit(&varName);
hr = pPropBag->Read(L”Description”, &varName, 0);
if (FAILED(hr))
{
hr = pPropBag->Read(L”FriendlyName”, &varName, 0);
}

8.Playback快进(慢进)的支持,可以通过IMediaSeeking::SetRate(或IMediaPosition::put_Rate)来实现。参数为1.0表示正常速度,2.0为双倍速度,0.5为半速;理论上参数为负数时能够实现倒放,但绝大部分Filter不支持倒放。对于Rate改变的响应,主要工作在负责打时间戳的Filter上,有可能是Parser Filter(如AVI Splitter),也有可能是Push模式下的Source Filter;这些Filter需要根据新的Rate重新打好时间戳。一般Decoder Filter不用对Rate改变做出响应,它们收到NewSegment后只要往下传递就行了,但对于一些可能需要改变时间戳的Decoder,则也需要考虑这个Rate的改变。Video Renderer一般也不需要关心Rate的改变,因为进来的Sample已经是按照新的Rate打好时间戳了;Audio Renderer需要对Rate进行更新,因为一般Audio Decoder不会因为Rate的改变而作相应的转换。还有一点,SetRate的调用之前,Filter Graph Manager会先将Filter Graph停止;调用之后,新的Sample时间戳从0开始。Playback Rate的改变会导致Audio很难听,所以此时最好将Audio静音处理。

9.Playback的单帧控制,可以通过Filter Graph Manager上获得的IVideoFrameStep接口来实现。Filter Graph Manager也主要是协同Video Renderer或者Overlay Mixer来实现单帧播放的。IVideoFrameStep接口方法不仅支持单帧跳进,也支持多帧的跳进。在使用接口方法时,最好先调用IVideoFrameStep::CanStep判断是否支持你指定的跳帧操作。调用IVideoFrameStep::Step实现跳进,其中第二个参数为实际实现跳帧功能的Filter,为NULL时Filter Graph Manager默认使用Video Renderer。当Step函数调用成功,Filter Graph Manager会向应用程序发送一个EC_STEP_COMPLETE事件,Filter Graph也自动转入Paused状态。

10.Mpeg1文件的播放可以直接使用微软的MPEG-1 Stream Splitter、MPEG Video Decoder和MPEG Audio Decoder。Video Decoder可以通过IMpegVideoDecoder接口(在mpgcodec.h中定义)进行参数修改,比如可以通过set_GreyScaleOutput设置输出图像是彩色的还是黑白的等;Audio Decoder可以通过IMpegAudioDecoder接口(在mpegtype.h中定义)进行参数设置,比如可以控制解码输出mono或是stereo,左右声道切换(注意:在解码输出mono的时候,不支持动态切换左右声道)等。
转)DirectShow实践经验杂谈(二)

11.AVI Splitter, Mpeg1 Stream splitter, Mpeg2 splitter只能工作在Pull模式;Mpeg2 Demultiplexer在非Window XP下工作在Push模式,而在Window XP下也能工作在Pull模式。所以,网络客户端接收压缩数据要进行解码时,一般将接收器Source Filter写成Pull模式(因为现成的Parser Filter大都只能工作在Pull模式)。如果要将接收器写成Push模式,则这个Source Filter送出来的数据应该是已经做过Parse处理的(即Video和Audio已分离)。

12.ACM(Audio Compression Manager)和VCM(Video Compression Manager)在DirectShow中都是通过包装Filter应用的。ACM Wrapper Filter,作为解压用时(主要进行Audio格式的转换,输出PCM数据),注册在”DirectShow Filters”目录下,Merit值为MERIT_NORMAL;作为压缩用时,各个Audio压缩器注册在”Audio Compressors”目录(CLSID_AudioCompressorCategory)下,Merit值为MERIT_DO_NOT_USE。注意:这里的Audio Compressor不能通过CoCreateInstance创建,而只能通过系统枚举。VCM包装Filter作为解压用时,即为AVI Decompressor Filter,一般实现Video从YUV到RGB格式的转换(注意:MPEG数据的压缩/解码都不是通过VCM包装Filter实现的);作为压缩用时,各压缩器注册在”Video Compressors”目录(CLSID_VideoCompressorCategory)下,Merit值为MERIT_DO_NOT_USE。注意:这里的Video Compressor也不能直接通过CoCreateInstance创建。

13.微软提供了两个Tee Filter:Smart Tee和Infinite Pin Tee Filter。前者有两个Output pin,且Preview pin输出的Sample已经去掉时间戳;后者,可以动态产生无数个Output pin,而且各个Output pin输出的Sample是完全一样的。

14.在Filtr Graph运行状态下,可以动态加入新的Filter,但不能删掉Filter,也不能断开Filter之间的Pin连接。Filter从Filter Graph中删除必须在Stopped状态,删除前将这个Filter的Pin断开不是必要的!(连接着的Pin可以通过IFilterGraph:isconnect断开,并且连接两头的Pin都要调用一次!)

15.AVI Mux问题。四种工作模式(通过Filter上的IConfigInterleaving::put_Mode设置):INTERLEAVE_NONE,对输入的数据不缓存,各帧数据按照它们到达Mux的顺序直接写入文件中,速度最快;INTERLEAVE_FULL,对Video和Audio的交叉打包精确控制,每次合成都要阻塞等待等量的Video、Audio数据,适合文件源的视频编辑等应用;INTERLEAVE_CAPTURE,处于前两种模式之间,尽量不使用数据缓存,在一个Pin上数据到达后等待另一个Pin数据到达时,可能会丢帧,适合Live Source Capture应用,注意此时Audio的Capture buffer应该小于0.5秒;INTERLEAVE_NONE_BUFFERED,仅在Win XP下用,类似于INTERLEAVE_NONE,只是这种模式生成的文件较小。还有另外一个接口方法IConfigInterleaving::put_Interleaving,设置打包频率和Audio的预留(Preroll),推荐打包频率为1秒钟一次,Audio预留750ms。

16.AVI 1.0文件大小有上限1GB,AVI 2.0文件没有这个限制。AVI Mux默认总是生成2.0的文件(1.0的文件主要是老的VFW生成的),也可以通过AVI Mux上的IConfigAviMux::SetOutputCompatibilityIndex来设置生成1.0的文件,以保持向后兼容。另一个接口方法IConfigAviMux::SetMasterStream,用以同步多个输入流,特别是在不同源的video、Audio Capture时,两个源的Capture rate可能有细微的差别。设置了Master stream之后,Mux会修改AVI文件的Playback rates(AVIStreamHeader结构的dwScale和dwRate两个成员变量)。推荐将Audio stream设为Master。

17.在同一个Filter Graph中,既有Live Source(比如Camcorder,capture devices,mpeg2 demux等,他们自带Reference Clock),又有Default DirectSound Device(声卡带有时钟),默认情况下,Filter Graph Manager选择Live Source的参考时钟。有时候会出现Live Source数据流传输了几十个Sample后会不在传送Sample出来。可能的原因是,有多个参考时钟时回放速率匹配问题。解决的方法是,选择声卡的时钟,或者设置整个Filter Graph不使用时钟。

18.0目前(DirectX8.1),微软并没有提供.dv文件的“拉”Filter。如果要使用DV文件,一个变通的方法是,将DV数据保存到AVI文件中。播放这种文件,AVI Splitter会识别出DV数据,并送DV Splitter(微软提供,Push模式)和DV Video Decoder(微软提供)进行解码。

18.1在回放含有DV的AVI文件时,我们发现,真正实现IMediaSeeking的是AVI Splitter,而不是DV Splitter(微软提供)。DV Splitter的Audio output pin或者Video Output pin均将IMediaSeeking的请求通过其Input pin传递到Up stream去了。

18.2如果自己写一个”DV Parser Filter”,需要注意的是,从Filter Source中拉数据,每次拉的数据大小一般是按512字节对齐的,所以不会是每次拉一个DV帧(PAL为144000字节,NTSC为120000字节)。而微软的DV Splitter输入的Sample要求是一帧一帧的数据(注意:如果每次送的数据太多了,会导致无法正常解码;如果送少了,导致后续Filter没有足够的数据进行解码,会发生Filter Graph的状态转换出错,即可能无法转入Paused状态),所以我们必须在DV Parser上对拉出的数据进行必要的缓存。而且,在处理Seek时,新的Seeking位置也要考虑“字节对齐”的问题。
其实,为DV文件写一个Source Filter,直接将DV数据从文件中读出后push出去,问题就要简单一点!

19.Tee Filter问题。微软提供的Smart Tee(没有源码)与Infinite Pin Tee Filter(有源码)相比,后者的性能要好一点。两者的区别是,前者将Preview pin出来的Sample进行了“去时间戳”处理,而后者只是简单地将一个Sample分别在各个output pin上输出。

20.写多进一出,或者是一进多出的Filter,基类可以选择CTransformFilter或者CTransInPlaceFilter,虽然它们在BaseClasses中的实现都是一进一出的。一般的做法为,保持原有的Input pin和Output pin为“主流”的链路,以这个标准Input pin的数据“流入”,来驱动协调其他Pin数据处理后,再输入。需要注意的是,必须重载基类Filter的GetPinCount、GetPin实现,以增加新的Pin;重载FindPin支持新加pin;重载Filter的Pause或Stop实现,因为可能还要同步新增加的pin的数据流(如果新增加的pin在Filter要转入Paused状态时还处于死循环或阻塞,则可能导致Filter状态转换失败,出现Frozen现象);新增加Input pin一般从CBaseInputPin上继承,Output pin从CBaseOutputPin上继承,而且一般都要重载以下几个函数的实现:CheckMediaType、Receive、EndOfStream、BeginFlush、EndFlush。
//———————————————————-
AVI文件播放原理(转)

Windows平台上,几乎所有的主流播放器软件在回放avi/ogm/mkv/asf/mpg时,都使
用微软的多媒体核心技术DirectX中的DirectShow。我所知道的两个例外是RealPlay 8
和DivX playa 2.0。所以常常会有人说某某用别的都放不了,用DivX playa就行,但是
却放不出字幕,这是很正常的事情,请往下看。
 
    DirectShow规划了一个媒体框架,在这个框架之下,所有的外围部件(也就是第三
方开发的)可以被这个框架进行调用,你可以想象一下winamp2.x中的插件,当然DS中的
插件的概念和功能要比winmap里的强上很多。
 
    我们先来分解一个典型的avi被播放的过程:假设这个avi的画面是DivX3.11编码
的,而音频使用的是AC3。
 
    首先,播放器检查你的DirectX版本是否足够,通常需要至少8.1。
 
    接下来,播放器在内部开始召唤DirectShow介入,DirectShow会根据文件自动试图
建立一个完整的Graph,在这个Graph里会先加入AVI Reader这个filter,它的功能用来
读取磁盘上的avi文件,然后加入AVI Splitter,将视频音频分离,接着从读入的AVI文
件的头中知道FourCC是div3,于是寻找是否有能够接管div3的filter,找不到的话,象
wmp6.4就会试图到微软的网站上去下载合适的filter。接着再寻找是否有能接管ac3的
filter,最后视频部分连接上video render将图像通过DirectDraw接口输出,而音频部
分则连上DirectSound输出。
 
    最终的Graph像这样:
 
                                      ___divx 3 decompressor —>vedio render
ur avi —>avi reader —>avi spliter/
                                     \___ac3 demuxer ———–>directsound
 
    这里面的每一部都是可控制的,例如把divx 3 decompressor拆下来,把divx 5或
ffdshow接上去等等。如果还有字幕需要播放的话,那么vobsub就会把自己插在divx 3
decompressor和vedio render之间。
 
    每个filter通过pin连接,pin有一个值,标记了他的连接优先级,比如一个电影用
divx3或divx5都能解码,但是divx5的pin值高,于是播放器就会使用divx5,至于各插件
应该在Graph中的什么位置,由filter自行协商判断。
 
    DirectShow在播放的时候维持着声画同步这一最重要的使命,所以常常可以看到有

的影片会跳帧,就是因为DS为了追赶声音而不得不丢弃画面,因为解码画面比解码声音
慢多了。
 
    而所谓的双声道,其实也就是avi里有两条音轨,象mmswitch这样的filter可以动态
处理切换音频流来完成转换,为什么rmvb不能用mmswitch?首先这种片子就只有一个音
轨,其次,还没有办法把rmvb纳入DS的框架中。

 

2004年06月12日

组件化程序设计方法解决软件二进制级别的重用性的比较好的方法!

组件是软件开发中一个可替换的单元,它封装了设计决策,具有标准化的可重用的公开接口。

跨进程、跨平台(网络)、跨语言——-组件的特点。

COM是一种通信标准

COM()Component Object Model组件对象模型)M$创建的一种二进制和网络通信标准,它允许人以两个组件互相通信,而不管它们是在什么什么计算机上运行,不管计算机是运行的什么操作系统(只要该操作系统支持COM),也不管该组件是用什么语言写的。

 要解决软件的复杂性,一个很自然的做法就是把一个大的工程分为不同的模块,把每一个模块保持一定的功能独立性,使得这些模块可以独立开发,当所有的模块开发完成后,再把它们组合在一起。

但是如何重用呢?常用的办法是使用DLL,但是DLL的发布和升级比较困难。

接口作为组件之间的联系的纽带,是组件合客户程序之间的协议,是组件软件的关键。因为客户无法访问对象的内部实现,也无法知道对象内部的数据结构,访问COM对象的唯一的方法就是通过接口。接口说白了就是一组API,只不过是封装的比较标准而已。

当另外的组件或者App(组件的客户程序)调用组件的功能时,它首先创建一个COM对象或者通过其他途径获得COM对象,然后通过该对象实现的接口来调用它所提供的服务。当所有的服务结束后,客户应该负责释放掉组件对象占用的资源,包括对象本身。

next coming……….

 

2004年06月10日

Video is the most common thing that SDL is used for, and so it has the most complete subsystem.. SDL framework makes cross-platform video programming child’s play. Well, almost

The following code will load a BMP file, and display it.

// step 2:sdl_first.cpp : Defines the entry point for the console application.
//

#include “stdio.h”
#include “stdlib.h”

#include “SDL.h”
#pragma comment(lib, “SDL.lib”)
#pragma comment(lib, “SDLmain.lib”)

void wxDisplay_bmp(SDL_Surface *screen,char *file_name)
{
    SDL_Surface *image;

 if(!screen)
  return;

    /* Load the BMP file into a surface */
    image = SDL_LoadBMP(file_name);
    if (image == NULL)
 {
        fprintf(stderr, “Couldn’t load %s: %s\n”, file_name, SDL_GetError());
        return;
    }

    /*
     * Palettized screen modes will have a default palette (a standard
     * 8*8*4 colour cube), but if the image is palettized as well we can
     * use that palette for a nicer colour matching
     */
    if (image->format->palette && screen->format->palette)
 {
  SDL_SetColors(screen, image->format->palette->colors, 0,
   image->format->palette->ncolors);
    }

    /* Blit onto the screen surface */
    if(SDL_BlitSurface(image, NULL, screen, NULL) < 0)
        fprintf(stderr, “BlitSurface error: %s\n”, SDL_GetError());

    SDL_UpdateRect(screen, 0, 0, image->w, image->h);

    /* Free the allocated BMP surface */
    SDL_FreeSurface(image);
}

int main(int argc, char* argv[])
{
 SDL_Surface *screen=0;
 Uint32 bpp;

 printf(“I am trying to initialize the SDL system….\n”);

 //init default ,video&aduio system
 if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO)<0)
 {
  printf(“Fail to initialize the SDL system,%s\n”,SDL_GetError());
  exit(-1);
 }
 printf(“SDL initlialize success!\n”);

 bpp=SDL_VideoModeOK(800,600,16,SDL_SWSURFACE);
 if(!bpp)
 {
  printf(“the mode is not available\n”);
  exit(1);
 }
 /*
     * Initialize the display in a 640×480 16-bit palettized mode,
     * requesting a software surface
     */
    screen = SDL_SetVideoMode(800, 600, 16, SDL_HWSURFACE|SDL_SWSURFACE|SDL_ANYFORMAT);
    if( screen == NULL )
 {
        fprintf(stderr, “Couldn’t set 640×480x8 video mode: %s\n”,
                        SDL_GetError());
        exit(1);
    }
 printf(“the screen has %d bit-per-pixel mode\n”,
  screen->format->BitsPerPixel);
 
 int done=0;

 while(done == 0)
 {
  SDL_Event event;

  while ( SDL_PollEvent(&event) )
  {
   if ( event.type == SDL_QUIT )
   {
    done = 1;
   }

   if ( event.type == SDL_KEYDOWN )
   {
    if ( event.key.keysym.sym == SDLK_ESCAPE )
    {
     done = 1;
    }
   }
  }

  wxDisplay_bmp(screen,”pic_girl.bmp”);
 }


 printf(“SDL quit!\n”);
 if(screen)
  SDL_FreeSurface(screen);
 SDL_Quit();
 
 return 0;
}

 

 

小知识:RGBYUV—-摘自《DirectShow实务精选》 作者:陆其明

 

计算机彩色显示器显示色彩的原理与彩色电视机一样,都是采用RRed)、GGreen)、BBlue)相加混色的原理:通过发射出三种不同强度的电子束,使屏幕内侧覆盖的红、绿、蓝磷光材料发光而产生色彩。这种色彩的表示方法称为RGB色彩空间表示(它也是多媒体计算机技术中用得最多的一种色彩空间表示方法)。

根据三基色原理,任意一种色光F都可以用不同分量的RGB三色相加混合而成。

 

F = r [ R ] + g [ G ] + b [ B ]

 

其中,rgb分别为三基色参与混合的系数。当三基色分量都为0(最弱)时混合为黑色光;而当三基色分量都为k(最强)时混合为白色光。调整rgb三个系数的值,可以混合出介于黑色光和白色光之间的各种各样的色光。

那么YUV又从何而来呢?在现代彩色电视系统中,通常采用三管彩色摄像机或彩色CCD摄像机进行摄像,然后把摄得的彩色图像信号经分色、分别放大校正后得到RGB,再经过矩阵变换电路得到亮度信号Y和两个色差信号RY(即U)、BY(即V),最后发送端将亮度和色差三个信号分别进行编码,用同一信道发送出去。这种色彩的表示方法就是所谓的YUV色彩空间表示。

采用YUV色彩空间的重要性是它的亮度信号Y和色度信号UV是分离的。如果只有Y信号分量而没有UV分量,那么这样表示的图像就是黑白灰度图像。彩色电视采用YUV空间正是为了用亮度信号Y解决彩色电视机与黑白电视机的兼容问题,使黑白电视机也能接收彩色电视信号。

YUVRGB相互转换的公式如下(RGB取值范围均为0-255):

 

Y = 0.299R + 0.587G + 0.114B

U = -0.147R – 0.289G + 0.436B

V = 0.615R – 0.515G – 0.100B

 

R = Y + 1.14V

G = Y – 0.39U – 0.58V

B = Y + 2.03U

 

DirectShow中,常见的RGB格式有RGB1RGB4RGB8RGB565RGB555RGB24RGB32ARGB32等;常见的YUV格式有YUY2YUYVYVYUUYVYAYUVY41PY411Y211IF09IYUVYV12YVU9YUV411YUV420等。作为视频媒体类型的辅助说明类型(Subtype),它们对应的GUID见表2.3

 

2.3 常见的RGBYUV格式

 

GUID

格式描述

MEDIASUBTYPE_RGB1

2色,每个像素用1位表示,需要调色板

MEDIASUBTYPE_RGB4

16色,每个像素用4位表示,需要调色板

MEDIASUBTYPE_RGB8

256色,每个像素用8位表示,需要调色板

MEDIASUBTYPE_RGB565

每个像素用16位表示,RGB分量分别使用5位、6位、5

MEDIASUBTYPE_RGB555

每个像素用16位表示,RGB分量都使用5位(剩下的1位不用)

MEDIASUBTYPE_RGB24

每个像素用24位表示,RGB分量各使用8

MEDIASUBTYPE_RGB32

每个像素用32位表示,RGB分量各使用8位(剩下的8位不用)

MEDIASUBTYPE_ARGB32

每个像素用32位表示,RGB分量各使用8位(剩下的8位用于表示Alpha通道值)

MEDIASUBTYPE_YUY2

YUY2格式,以4:2:2方式打包

MEDIASUBTYPE_YUYV

YUYV格式(实际格式与YUY2相同)

MEDIASUBTYPE_YVYU

YVYU格式,以4:2:2方式打包

MEDIASUBTYPE_UYVY

UYVY格式,以4:2:2方式打包

MEDIASUBTYPE_AYUV

Alpha通道的4:4:4 YUV格式

MEDIASUBTYPE_Y41P

Y41P格式,以4:1:1方式打包

MEDIASUBTYPE_Y411

Y411格式(实际格式与Y41P相同)

MEDIASUBTYPE_Y211

Y211格式

MEDIASUBTYPE_IF09

IF09格式

MEDIASUBTYPE_IYUV

IYUV格式

MEDIASUBTYPE_YV12

YV12格式

MEDIASUBTYPE_YVU9

YVU9格式

 

下面分别介绍各种RGB格式。

 

¨ RGB1RGB4RGB8都是调色板类型的RGB格式,在描述这些媒体类型的格式细节时,通常会在

BITMAPINFOHEADER数据结构后面跟着一个调色板(定义一系列颜色)。它们的图像数据并不是真正

的颜色值,而是当前像素颜色值在调色板中的索引。以RGB12色位图)为例,比如它的调色板中

定义的两种颜色值依次为0×000000(黑色)和0xFFFFFF(白色),那么图像数据001101010111…

(每个像素用1位表示)表示对应各像素的颜色为:黑黑白白黑白黑白黑白白白

 

¨ RGB565使用16位表示一个像素,这16位中的5位用于R6位用于G5位用于B。程序中通常使用一

个字(WORD,一个字等于两个字节)来操作一个像素。当读出一个像素后,这个字的各个位意义如

下:

     高字节              低字节

R R R R R G G G     G G G B B B B B
可以组合使用屏蔽字和移位操作来得到RGB各分量的值:
 
#define RGB565_MASK_RED    0xF800
#define RGB565_MASK_GREEN  0x07E0
#define RGB565_MASK_BLUE   0x001F
R = (wPixel & RGB565_MASK_RED) >> 11;   // 取值范围0-31
G = (wPixel & RGB565_MASK_GREEN) >> 5;  // 取值范围0-63
B =  wPixel & RGB565_MASK_BLUE;         // 取值范围0-31
 

¨ RGB555是另一种16位的RGB格式,RGB分量都用5位表示(剩下的1位不用)。使用一个字读出一个

像素后,这个字的各个位意义如下:

     高字节             低字节

X R R R R G G       G G G B B B B B       X表示不用,可以忽略)

可以组合使用屏蔽字和移位操作来得到RGB各分量的值:
 
#define RGB555_MASK_RED    0x7C00
#define RGB555_MASK_GREEN  0x03E0
#define RGB555_MASK_BLUE   0x001F
R = (wPixel & RGB555_MASK_RED) >> 10;   // 取值范围0-31
G = (wPixel & RGB555_MASK_GREEN) >> 5;  // 取值范围0-31
B =  wPixel & RGB555_MASK_BLUE;         // 取值范围0-31
 
¨ RGB24使用24位来表示一个像素,RGB分量都用8位表示,取值范围为0-255。注意在内存中RGB各分
量的排列顺序为:BGR BGR BGR…。通常可以使用RGBTRIPLE数据结构来操作一个像素,它的定义
为:
 
typedef struct tagRGBTRIPLE { 
  BYTE rgbtBlue;    // 蓝色分量
  BYTE rgbtGreen;   // 绿色分量
  BYTE rgbtRed;     // 红色分量
} RGBTRIPLE;
 
¨ RGB32使用32位来表示一个像素,RGB分量各用去8位,剩下的8位用作Alpha通道或者不用。(ARGB32
就是带Alpha通道的RGB32。)注意在内存中RGB各分量的排列顺序为:BGRA BGRA BGRA…。通常可以
使用RGBQUAD数据结构来操作一个像素,它的定义为:
 
typedef struct tagRGBQUAD {
  BYTE    rgbBlue;      // 蓝色分量
  BYTE    rgbGreen;     // 绿色分量
  BYTE    rgbRed;       // 红色分量
  BYTE    rgbReserved;  // 保留字节(用作Alpha通道或忽略)
} RGBQUAD;
 
下面介绍各种YUV格式。YUV格式通常有两大类:打包(packed)格式和平面(planar)格式。前者
YUV分量存放在同一个数组中,通常是几个相邻的像素组成一个宏像素(macro-pixel);而后者
使用三个数组分开存放YUV三个分量,就像是一个三维平面一样。表2.3中的YUY2Y211都是打包格式,
IF09YVU9都是平面格式。(注意:在介绍各种具体格式时,YUV各分量都会带有下标,如Y0U0V0
表示第一个像素的YUV分量,Y1U1V1表示第二个像素的YUV分量,以此类推。)
 
¨ YUY2(和YUYV)格式为每个像素保留Y分量,而UV分量在水平方向上每两个像素采样一次。一个
宏像素为4个字节,实际表示2个像素。(4:2:2的意思为一个宏像素中有4Y分量、2U分量和2
V分量。)图像数据中YUV分量排列顺序如下:
Y0 U0 Y1 V0    Y2 U2 Y3 V2 …
 
¨ YVYU格式跟YUY2类似,只是图像数据中YUV分量的排列顺序有所不同:
Y0 V0 Y1 U0    Y2 V2 Y3 U2 …
 
¨ UYVY格式跟YUY2类似,只是图像数据中YUV分量的排列顺序有所不同:
U0 Y0 V0 Y1    U2 Y2 V2 Y3 …
 
¨ AYUV格式带有一个Alpha通道,并且为每个像素都提取YUV分量,图像数据格式如下:
A0 Y0 U0 V0    A1 Y1 U1 V1 …
 
¨ Y41P(和Y411)格式为每个像素保留Y分量,而UV分量在水平方向上每4个像素采样一次。一个
宏像素为12个字节,实际表示8个像素。图像数据中YUV分量排列顺序如下:
U0 Y0 V0 Y1    U4 Y2 V4 Y3    Y4 Y5 Y6 Y8 … 
 
¨ Y211格式在水平方向上Y分量每2个像素采样一次,而UV分量每4个像素采样一次。一个宏像素为
4个字节,实际表示4个像素。图像数据中YUV分量排列顺序如下:
Y0 U0 Y2 V0    Y4 U4 Y6 V4 …
 
¨ YVU9格式为每个像素都提取Y分量,而在UV分量的提取时,首先将图像分成若干个4 x 4的宏块,
然后每个宏块提取一个U分量和一个V分量。图像数据存储时,首先是整幅图像的Y分量数组,然后
就跟着U分量数组,以及V分量数组。IF09格式与YVU9类似。
 
¨ IYUV格式为每个像素都提取Y分量,而在UV分量的提取时,首先将图像分成若干个2 x 2的宏块,
然后每个宏块提取一个U分量和一个V分量。YV12格式与IYUV类似。
 
¨ YUV411YUV420格式多见于DV数据中,前者用于NTSC制,后者用于PAL制。YUV411为每个像素都
提取Y分量,而UV分量在水平方向上每4个像素采样一次。YUV420并非V分量采样为0,而是跟YUV411
相比,在水平方向上提高一倍色差采样频率,在垂直方向上以U/V间隔的方式减小一半色差采样,
如图2.12所示。 
     

2.12 YUV411YUV420的采样格式

2004年06月09日

    Simple DirectMedia Layer is a cross-platform multimedia library designed to provide low level access to audio, keyboard, mouse, joystick, 3D hardware via OpenGL, and 2D video framebuffer. It is used by MPEG playback software, emulators, and many popular games.

Simple DirectMedia Layer supports Linux, Windows, BeOS, MacOS Classic, MacOS X, FreeBSD, OpenBSD, BSD/OS, Solaris, IRIX, and QNX. There is also code, but no official support, for Windows CE, AmigaOS, Dreamcast, Atari, NetBSD, AIX, OSF/Tru64, RISC OS, and SymbianOS.

SDL is written in C, but works with C++ natively, and has bindings to several other languages, including Ada, Eiffel, Java, Lua, ML, Perl, PHP, Pike, Python, and Ruby.

SDL is composed of eight subsystems – Audio, CDROM, Event Handling, File I/O, Joystick Handling, Threading, Timers and Video. SDL_Init must be called before any other SDL function. It automatically initializes the Event Handling, File I/O and Threading subsystems and it takes a parameter specifying which other subsystems to initialize. Here are the para that you can pass:

SDL_INIT_TIMER
SDL_INIT_AUDIO
SDL_INIT_VIDEO
SDL_INIT_CDROM
SDL_INIT_JOYSTICK
SDL_INIT_NOPARACHUTE
SDL_INIT_EVENTTHREAD
SDL_INIT_EVERYTHING

SDL_Init is complemented by SDL_Quit (and SDL_QuitSubSystem). SDL_Quit shuts down all subsystems, including the default ones. It should always be called before a SDL application exits. every time an error occurs within SDL an error message is stored which can be retrieved using SDL_GetError.

SDL runtime liabary for win32(http://www.libsdl.org/download-1.2.php), the configure is very simple.

// sdl_first.cpp : Defines the entry point for the console application.

//

 

#include “stdio.h”

#include “stdlib.h”

 

#include “SDL.h”

#pragma comment(lib, “SDL.lib”)

#pragma comment(lib, “SDLmain.lib”)

 

int main(int argc, char* argv[])

{

   printf(“I am trying to initialize the SDL system….\n”);

 

   //init default ,video&aduio system

   if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO)<0)

   {

          printf(“Fail to initialize the SDL system,%s\n”,SDL_GetError());

          exit(-1);

   }

   printf(“SDL initlialize success!\n”);

  

   printf(“SDL quit!\n”);

   SDL_Quit();

  

   return 0;

}

XXX:Must select Multi-Threaded DLL or Multi-Threaded Debug DLL if you wish to use debugging in project setting dialog.

/*

If you would want to run SDL programs, then you MUST have the file SDL.dll inside c:\windows\system (win 95, 98, ME) or c:\windows\system32 (on windows NT, 2000 and XP). OR you may have the file SDL.dll in the SAME folder as the executable of your program. If you would want to distirbute your SDL programs among friends then you MUST also give them the file SDL.dll.

*/

///////////////////////////////////////////////////////////////////////////////////////

The implementation of SDL_mutex

#ifndef _SDL_mutex_h
#define _SDL_mutex_h

/* Functions to provide thread synchronization primitives

 These are independent of the other SDL routines.
*/

#include “SDL_main.h”
#include “SDL_types.h”

#include “begin_code.h”
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern “C” {
#endif

/* Synchronization functions which can time out return this value
   if they time out.
*/
#define SDL_MUTEX_TIMEDOUT 1

/* This is the timeout value which corresponds to never time out */
#define SDL_MUTEX_MAXWAIT (~(Uint32)0)

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Mutex functions                                               */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* The SDL mutex structure, defined in SDL_mutex.c */
struct SDL_mutex;
typedef struct SDL_mutex SDL_mutex;

/* Create a mutex, initialized unlocked */
extern DECLSPEC SDL_mutex * SDLCALL SDL_CreateMutex(void);

/* Lock the mutex  (Returns 0, or -1 on error) */
#define SDL_LockMutex(m) SDL_mutexP(m)
extern DECLSPEC int SDLCALL SDL_mutexP(SDL_mutex *mutex);

/* Unlock the mutex  (Returns 0, or -1 on error)
   It is an error to unlock a mutex that has not been locked by
   the current thread, and doing so results in undefined behavior.
 */
#define SDL_UnlockMutex(m) SDL_mutexV(m)
extern DECLSPEC int SDLCALL SDL_mutexV(SDL_mutex *mutex);

/* Destroy a mutex */
extern DECLSPEC void SDLCALL SDL_DestroyMutex(SDL_mutex *mutex);

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Semaphore functions                                           */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* The SDL semaphore structure, defined in SDL_sem.c */
struct SDL_semaphore;
typedef struct SDL_semaphore SDL_sem;

/* Create a semaphore, initialized with value, returns NULL on failure. */
extern DECLSPEC SDL_sem * SDLCALL SDL_CreateSemaphore(Uint32 initial_value);

/* Destroy a semaphore */
extern DECLSPEC void SDLCALL SDL_DestroySemaphore(SDL_sem *sem);

/* This function suspends the calling thread until the semaphore pointed
 * to by sem has a positive count. It then atomically decreases the semaphore
 * count.
 */
extern DECLSPEC int SDLCALL SDL_SemWait(SDL_sem *sem);

/* Non-blocking variant of SDL_SemWait(), returns 0 if the wait succeeds,
   SDL_MUTEX_TIMEDOUT if the wait would block, and -1 on error.
*/
extern DECLSPEC int SDLCALL SDL_SemTryWait(SDL_sem *sem);

/* Variant of SDL_SemWait() with a timeout in milliseconds, returns 0 if
   the wait succeeds, SDL_MUTEX_TIMEDOUT if the wait does not succeed in
   the allotted time, and -1 on error.
   On some platforms this function is implemented by looping with a delay
   of 1 ms, and so should be avoided if possible.
*/
extern DECLSPEC int SDLCALL SDL_SemWaitTimeout(SDL_sem *sem, Uint32 ms);

/* Atomically increases the semaphore’s count (not blocking), returns 0,
   or -1 on error.
 */
extern DECLSPEC int SDLCALL SDL_SemPost(SDL_sem *sem);

/* Returns the current count of the semaphore */
extern DECLSPEC Uint32 SDLCALL SDL_SemValue(SDL_sem *sem);

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Condition variable functions                                  */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* The SDL condition variable structure, defined in SDL_cond.c */
struct SDL_cond;
typedef struct SDL_cond SDL_cond;

/* Create a condition variable */
extern DECLSPEC SDL_cond * SDLCALL SDL_CreateCond(void);

/* Destroy a condition variable */
extern DECLSPEC void SDLCALL SDL_DestroyCond(SDL_cond *cond);

/* Restart one of the threads that are waiting on the condition variable,
   returns 0 or -1 on error.
 */
extern DECLSPEC int SDLCALL SDL_CondSignal(SDL_cond *cond);

/* Restart all threads that are waiting on the condition variable,
   returns 0 or -1 on error.
 */
extern DECLSPEC int SDLCALL SDL_CondBroadcast(SDL_cond *cond);

/* Wait on the condition variable, unlocking the provided mutex.
   The mutex must be locked before entering this function!
   Returns 0 when it is signaled, or -1 on error.
 */
extern DECLSPEC int SDLCALL SDL_CondWait(SDL_cond *cond, SDL_mutex *mut);

/* Waits for at most ‘ms’ milliseconds, and returns 0 if the condition
   variable is signaled, SDL_MUTEX_TIMEDOUT if the condition is not
   signaled in the allotted time, and -1 on error.
   On some platforms this function is implemented by looping with a delay
   of 1 ms, and so should be avoided if possible.
*/
extern DECLSPEC int SDLCALL SDL_CondWaitTimeout(SDL_cond *cond, SDL_mutex *mutex, Uint32 ms);

/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include “close_code.h”

#endif /* _SDL_mutex_h */

2004年06月07日

用GDB调试 GCC 程序

作者:Rick McMullin 发文时间:2004.06.02
Linux 包含了一个叫 gdb 的 GNU 调试程序. gdb 是一个用来调试 C 和 C++ 程序的强力调试器. 它使你能在程序运行时观察程序的内部结构和内存的使用情况. 以下是 gdb 所提供的一些功能:

它使你能监视你程序中变量的值.
它使你能设置断点以使程序在指定的代码行上停止执行.
它使你能一行行的执行你的代码.
在命令行上键入 gdb 并按回车键就可以运行 gdb 了, 如果一切正常的话, gdb 将被启动并且你将在屏幕上看到类似的内容:

 GDB is free software and you are welcome to distribute copies of it under certain conditions; type “show copying” to see the conditions. There is absolutely no warranty for GDB; type “show warranty” for details.GDB 4.14 (i486-slakware-linux), Copyright 1995 Free Software Foundation, Inc. (gdb)
当你启动 gdb 后, 你能在命令行上指定很多的选项. 你也可以以下面的方式来运行 gdb :
gdb
当你用这种方式运行 gdb , 你能直接指定想要调试的程序. 这将告诉gdb 装入名为 fname 的可执行文件. 你也可以用 gdb 去检查一个因程序异常终止而产生的 core 文件, 或者与一个正在运行的程序相连. 你可以参考 gdb 指南页或在命令行上键入 gdb -h 得到一个有关这些选项的说明的简单列表.

gdb的使用(debug)
    gdb是Linux下一个常用的调试工具.要想使用它对程序进行调试,在编译的时候注意要加上选项 -g,
gcc -g hello.c -o hello.这样得到的可执行文件中就加入了调试信息,通常文件也会变得很大.下面是它的用法:
  gdb hello// 调试hello
  gdb break main// break,设置断点,此处断点设在程序的开头,main函数的入口处
  gdb r// 开始运行程序,在断点处停止,本处为程序的开始
  gdb s// step,开始单步调试程序了,同时会打印出此处程序的源代码
  gdb print var// 打印变量的当前值,程序不会向前执行,var为变量名
  gdb r或者gdb q// 错误排除,让程序直接运行结束或直接退出.(gdb r运行完也不会退出gdb,请再输入一个q)

makefile的使用
    makefile是用于多源代码文件项目管理的.举例说明,一个可执行文件abc由a.o,b.o,c.o连接而成;而a.o由a1.c,a2.c,a3.c编译而成,b.o由b1.c,b2.c,b3.c编译而成,c.o由c1.c,c2.c,c3.c编译而成.够复杂的.我们需要
  gcc a1.c a2.c a3.c -c a.o
  gcc b1.c b2.c b3.c -c b.o
  gcc c1.c c2.c c3.c -c c.o
  gcc a.o b.o c.o -o abc
    如果仅仅一个源文件a1.c发生变化,我们必须重新编译其它没有发生变化的程序,做无用功.这里的例子仍然很小,如果是一个有数百个源代码组成的工程,简化编译过程将十分必要.makefile由此而生.
    makefile由依赖关系和编译命令组成.依赖关系的写法是
    目标 : 依赖的文件1,依赖的文件2,依赖的文件3…
    编译命令直接写在依赖关系的下一行,注意,以一个tab退格键开头,四个空格不行
    我们以上面的例子写一个makefile文件.(makefile的默认文件名就是makefile,无后缀名)
  vi Makefile
  abc : a.o b.o c.o
 gcc a.o b.o c.o -o abc
  a.o : a1.c a2.c a3.c
 gcc a1.c a2.c a3.c -c a.o
  b.o : b1.c b2.c b3.c
 gcc b1.c b2.c b3.c -c b.o
c.o : c1.c c2.c c3.c
 gcc c1.c c2.c c3.c -c c.o
  执行makefile,系统就会根据源文件的最后修改时间决定,那些需要重新编译,而哪些不要.

为调试编译代码(Compiling Code for Debugging)
为了使 gdb 正常工作, 你必须使你的程序在编译时包含调试信息. 调试信息包含你程序里的每个变量的类型和在可执行文件里的地址映射以及源代码的行号. gdb 利用这些信息使源代码和机器码相关联. 在编译时用 -g 选项打开调试选项.

gdb 基本命令

gdb 支持很多的命令使你能实现不同的功能. 这些命令从简单的文件装入到允许你检查所调用的堆栈内容的复杂命令, 表1列出了你在用 gdb 调试时会用到的一些命令.

表1. 基本 gdb 命令.
命 令 描 述
file 装入想要调试的可执行文件
kill 终止正在调试的程序
list 列出产生执行文件的源代码的一部分
next 执行一行源代码但不进入函数内部
step 执行一行源代码而且进入函数内部
run 执行当前被调试的程序
quit 终止 gdb
watch 使你能监视一个变量的值而不管它何时被改变
break 在代码里设置断点, 这将使程序执行到这里时被挂起
make 使你能不退出 gdb 就可以重新产生可执行文件
shell 使你能不离开 gdb 就执行 UNIX shell 命令

gdb 支持很多与 UNIX shell 程序一样的命令编辑特征. 你能象在 bash 或 tcsh里那样按 Tab 键让 gdb 帮你补齐一个惟一的命令, 如果不惟一的话 gdb 会列出所有匹配的命令. 你也能用光标键上下翻动历史命令.

gdb 应用举例
本节用一个实例教你一步步的用 gdb 调试程序. 被调试的程序相当的简单, 但它展示了 gdb 的典型应用.
下面列出了将被调试的程序. 这个程序被称为 greeting , 它显示一个简单的问候, 再用反序将它列出.
 #include  main ()
{
 char my_string[] = “hello there”;
 my_print (my_string);
 my_print2 (my_string);
}
void my_print (char *string)
{
 printf (“The string is %s\n”, string);
}
void my_print2 (char *string)
{
 char *string2;
 int size, i; size = strlen (string);
 string2 = (char *) malloc (size + 1);
 for (i = 0; i < size; i++)
  string2[size - i] = string[i];
 string2[size+1] = `\0′;
 printf (“The string printed backward is %s\n”, string2);
}

用下面的命令编译它:
gcc -o test test.c
这个程序执行时显示如下结果:
The string is hello there
The string printed backward is

输出的第一行是正确的, 但第二行打印出的东西并不是我们所期望的. 我们所设想的输出应该是:
The string printed backward is ereht olleh

由于某些原因, my_print2 函数没有正常工作. 让我们用 gdb 看看问题究竟出在哪儿, 先键入如下命令:
gdb greeting

注意: 记得在编译 greeting 程序时把调试选项打开.
如果你在输入命令时忘了把要调试的程序作为参数传给 gdb , 你可以在 gdb 提示符下用 file 命令来载入它:
(gdb) file greeting
这个命令将载入 greeting 可执行文件就象你在 gdb 命令行里装入它一样.
这时你能用 gdb 的 run 命令来运行 greeting 了. 当它在 gdb 里被运行后结果大约会象这样:
 (gdb) run Starting program: /root/greeting The string is hello there The string printed backward is Program exited with code 041
这个输出和在 gdb 外面运行的结果一样. 问题是, 为什么反序打印没有工作? 为了找出症结所在, 我们可以在 my_print2 函数的 for 语句后设一个断点, 具体的做法是在 gdb 提示符下键入 list 命令三次, 列出源代码:
(gdb) list
(gdb) list
(gdb) list

技巧: 在 gdb 提示符下按回车健将重复上一个命令.
第一次键入 list 命令的输出如下:
 1 #include  2 3 main () 4 { 5 char my_string[] = “hello there”; 6 7 my_print (my_string); 8 my_print2 (my_string); 9 } 10
如果按下回车, gdb 将再执行一次 list 命令, 给出下列输出:
 11 my_print (char *string) 12 { 13 printf (“The string is %s\n”, string); 14 } 15 16 my_print2 (char *string) 17 { 18 char *string2; 19 int size, i; 20
再按一次回车将列出 greeting 程序的剩余部分:
 21 size = strlen (string); 22 string2 = (char *) malloc (size + 1); 23 for (i = 0; i < size; i++) 24 string2[size - i] = string[i]; 25 string2[size+1] = `\0′; 26 printf (“The string printed backward is %s\n”, string2); 27 }
根据列出的源程序, 你能看到要设断点的地方在第24行, 在 gdb 命令行提示符下键入如下命令设置断点:
(gdb) break 24
gdb 将作出如下的响应:
Breakpoint 1 at 0×139: file greeting.c, line 24
(gdb)
现在再键入 run 命令, 将产生如下的输出:
 Starting program: /root/greeting The string is hello there Breakpoint 1, my_print2 (string = 0xbfffdc4 “hello there”) at greeting.c :24 24 string2[size-i]=string[i]
你能通过设置一个观察 string2[size - i] 变量的值的观察点来看出错误是怎样产生的, 做法是键入:
(gdb) watch string2[size - i]
gdb 将作出如下回应:
Watchpoint 2: string2[size - i]
现在可以用 next 命令来一步步的执行 for 循环了:
(gdb) next
经过第一次循环后, gdb 告诉我们 string2[size - i] 的值是 `h`. gdb 用如下的显示来告诉你这个信息:
 Watchpoint 2, string2[size - i] Old value = 0 `\000′ New value = 104 `h’ my_print2(string = 0xbfffdc4 “hello there”) at greeting.c:23 23 for (i=0; i
这个值正是期望的. 后来的数次循环的结果都是正确的. 当 i=10 时, 表达式 string2[size - i] 的值等于 `e`, size – i 的值等于 1, 最后一个字符已经拷到新串里了.
如果你再把循环执行下去, 你会看到已经没有值分配给 string2[0] 了, 而它是新串的第一个字符, 因为 malloc 函数在分配内存时把它们初始化为空(null)字符. 所以 string2 的第一个字符是空字符. 这解释了为什么在打印 string2 时没有任何输出了.
现在找出了问题出在哪里, 修正这个错误是很容易的. 你得把代码里写入 string2 的第一个字符的的偏移量改为 size – 1 而不是 size. 这是因为 string2 的大小为 12, 但起始偏移量是 0, 串内的字符从偏移量 0 到 偏移量 10, 偏移量 11 为空字符保留.
为了使代码正常工作有很多种修改办法. 一种是另设一个比串的实际大小小 1 的变量. 这是这种解决办法的代码:
 #include  main ()
 {
  char my_string[] = “hello there”;
  my_print (my_string);
  my_print2 (my_string);
 }
 my_print (char *string)
 {
  printf (“The string is %s\n”, string);
 }
 my_print2 (char *string)
 {
  char *string2;
  int size, size2, i;
  size = strlen (string);
  size2 = size -1;
  string2 = (char *) malloc (size + 1);
  for (i = 0; i < size; i++)
   string2[size2 - i] = string[i];
  string2[size] = `\0′;
  printf (“The string printed backward is %s\n”, string2);
 }

  GCC精彩之旅
作者:肖文鹏 发文时间:2004.03.22
在为Linux开发应用程序时,绝大多数情况下使用的都是C语言,因此几乎每一位Linux程序员面临的首要问题都是如何灵活运用C编译器。目前Linux下最常用的C语言编译器是GCC(GNU Compiler Collection),它是GNU项目中符合ANSI C标准的编译系统,能够编译用C、C++和Object C等语言编写的程序。GCC不仅功能非常强大,结构也异常灵活。最值得称道的一点就是它可以通过不同的前端模块来支持各种语言,如Java、Fortran、Pascal、Modula-3和Ada等。

开放、自由和灵活是Linux的魅力所在,而这一点在GCC上的体现就是程序员通过它能够更好地控制整个编译过程。在使用GCC编译程序时,编译过程可以被细分为四个阶段:

◆ 预处理(Pre-Processing)

◆ 编译(Compiling)

◆ 汇编(Assembling)

◆ 链接(Linking)

Linux程序员可以根据自己的需要让GCC在编译的任何阶段结束,以便检查或使用编译器在该阶段的输出信息,或者对最后生成的二进制文件进行控制,以便通过加入不同数量和种类的调试代码来为今后的调试做好准备。和其它常用的编译器一样,GCC也提供了灵活而强大的代码优化功能,利用它可以生成执行效率更高的代码。

GCC提供了30多条警告信息和三个警告级别,使用它们有助于增强程序的稳定性和可移植性。此外,GCC还对标准的C和C++语言进行了大量的扩展,提高程序的执行效率,有助于编译器进行代码优化,能够减轻编程的工作量。

GCC起步

在学习使用GCC之前,下面的这个例子能够帮助用户迅速理解GCC的工作原理,并将其立即运用到实际的项目开发中去。首先用熟悉的编辑器输入清单1所示的代码:

清单1:hello.c

 #include <stdio.h>
 int main(void)
 {
  printf (“Hello world, Linux programming!\n”);
  return 0;
 }
 
然后执行下面的命令编译和运行这段程序:

 # gcc hello.c -o hello # ./hello Hello world, Linux programming!
 
从程序员的角度看,只需简单地执行一条GCC命令就可以了,但从编译器的角度来看,却需要完成一系列非常繁杂的工作。首先,GCC需要调用预处理程序cpp,由它负责展开在源文件中定义的宏,并向其中插入“#include”语句所包含的内容;接着,GCC会调用ccl和as将处理后的源代码编译成目标代码;最后,GCC会调用链接程序ld,把生成的目标代码链接成一个可执行程序。

为了更好地理解GCC的工作过程,可以把上述编译过程分成几个步骤单独进行,并观察每步的运行结果。第一步是进行预编译,使用-E参数可以让GCC在预处理结束后停止编译过程:

 # gcc -E hello.c -o hello.i
 
此时若查看hello.cpp文件中的内容,会发现stdio.h的内容确实都插到文件里去了,而其它应当被预处理的宏定义也都做了相应的处理。下一步是将hello.i编译为目标代码,这可以通过使用-c参数来完成:

 # gcc -c hello.i -o hello.o
 
GCC默认将.i文件看成是预处理后的C语言源代码,因此上述命令将自动跳过预处理步骤而开始执行编译过程,也可以使用-x参数让GCC从指定的步骤开始编译。最后一步是将生成的目标文件链接成可执行文件:

 # gcc hello.o -o hello
 
在采用模块化的设计思想进行软件开发时,通常整个程序是由多个源文件组成的,相应地也就形成了多个编译单元,使用GCC能够很好地管理这些编译单元。假设有一个由foo1.c和foo2.c两个源文件组成的程序,为了对它们进行编译,并最终生成可执行程序foo,可以使用下面这条命令:

 # gcc foo1.c foo2.c -o foo
 
如果同时处理的文件不止一个,GCC仍然会按照预处理、编译和链接的过程依次进行。如果深究起来,上面这条命令大致相当于依次执行如下三条命令:

 # gcc -c foo1.c -o foo1.o # gcc -c foo2.c -o foo2.o # gcc foo1.o foo2.o -o foo
 
在编译一个包含许多源文件的工程时,若只用一条GCC命令来完成编译是非常浪费时间的。假设项目中有100个源文件需要编译,并且每个源文件中都包含10000行代码,如果像上面那样仅用一条GCC命令来完成编译工作,那么GCC需要将每个源文件都重新编译一遍,然后再全部连接起来。很显然,这样浪费的时间相当多,尤其是当用户只是修改了其中某一个文件的时候,完全没有必要将每个文件都重新编译一遍,因为很多已经生成的目标文件是不会改变的。要解决这个问题,关键是要灵活运用GCC,同时还要借助像Make这样的工具。

警告提示功能

GCC包含完整的出错检查和警告提示功能,它们可以帮助Linux程序员写出更加专业和优美的代码。先来读读清单2所示的程序,这段代码写得很糟糕,仔细检查一下不难挑出很多毛病:

◆main函数的返回值被声明为void,但实际上应该是int;

◆使用了GNU语法扩展,即使用long long来声明64位整数,不符合ANSI/ISO C语言标准;

◆main函数在终止前没有调用return语句。

清单2:illcode.c

 #include <stdio.h>
 void main(void)
 {
  long long int var = 1;
  printf(“It is not standard C code!\n”);
 }

下面来看看GCC是如何帮助程序员来发现这些错误的。当GCC在编译不符合ANSI/ISO C语言标准的源代码时,如果加上了-pedantic选项,那么使用了扩展语法的地方将产生相应的警告信息:

 # gcc -pedantic illcode.c -o illcode illcode.c: In function `main’: illcode.c:9: ISO C89 does not support `long long’ illcode.c:8: return type of `main’ is not `int’

需要注意的是,-pedantic编译选项并不能保证被编译程序与ANSI/ISO C标准的完全兼容,它仅仅只能用来帮助Linux程序员离这个目标越来越近。或者换句话说,-pedantic选项能够帮助程序员发现一些不符合ANSI/ISO C标准的代码,但不是全部,事实上只有ANSI/ISO C语言标准中要求进行编译器诊断的那些情况,才有可能被GCC发现并提出警告。

除了-pedantic之外,GCC还有一些其它编译选项也能够产生有用的警告信息。这些选项大多以-W开头,其中最有价值的当数-Wall了,使用它能够使GCC产生尽可能多的警告信息:

 # gcc -Wall illcode.c -o illcode illcode.c:8: warning: return type of `main’ is not `int’ illcode.c: In function `main’: illcode.c:9: warning: unused variable `var’

GCC给出的警告信息虽然从严格意义上说不能算作是错误,但却很可能成为错误的栖身之所。一个优秀的Linux程序员应该尽量避免产生警告信息,使自己的代码始终保持简洁、优美和健壮的特性。

在处理警告方面,另一个常用的编译选项是-Werror,它要求GCC将所有的警告当成错误进行处理,这在使用自动编译工具(如Make等)时非常有用。如果编译时带上-Werror选项,那么GCC会在所有产生警告的地方停止编译,迫使程序员对自己的代码进行修改。只有当相应的警告信息消除时,才可能将编译过程继续朝前推进。执行情况如下:

 # gcc -Wall -Werror illcode.c -o illcode cc1: warnings being treated as errors illcode.c:8: warning: return type of `main’ is not `int’ illcode.c: In function `main’: illcode.c:9: warning: unused variable `var’

对Linux程序员来讲,GCC给出的警告信息是很有价值的,它们不仅可以帮助程序员写出更加健壮的程序,而且还是跟踪和调试程序的有力工具。建议在用GCC编译源代码时始终带上-Wall选项,并把它逐渐培养成为一种习惯,这对找出常见的隐式编程错误很有帮助。

库依赖

在Linux下开发软件时,完全不使用第三方函数库的情况是比较少见的,通常来讲都需要借助一个或多个函数库的支持才能够完成相应的功能。从程序员的角度看,函数库实际上就是一些头文件(.h)和库文件(.so或者.a)的集合。虽然Linux下的大多数函数都默认将头文件放到/usr/include/目录下,而库文件则放到/usr/lib/目录下,但并不是所有的情况都是这样。正因如此,GCC在编译时必须有自己的办法来查找所需要的头文件和库文件。

GCC采用搜索目录的办法来查找所需要的文件,-I选项可以向GCC的头文件搜索路径中添加新的目录。例如,如果在/home/xiaowp/include/目录下有编译时所需要的头文件,为了让GCC能够顺利地找到它们,就可以使用-I选项:

 # gcc foo.c -I /home/xiaowp/include -o foo

同样,如果使用了不在标准位置的库文件,那么可以通过-L选项向GCC的库文件搜索路径中添加新的目录。例如,如果在/home/xiaowp/lib/目录下有链接时所需要的库文件libfoo.so,为了让GCC能够顺利地找到它,可以使用下面的命令:

 # gcc foo.c -L /home/xiaowp/lib -lfoo -o foo

值得好好解释一下的是-l选项,它指示GCC去连接库文件libfoo.so。Linux下的库文件在命名时有一个约定,那就是应该以lib三个字母开头,由于所有的库文件都遵循了同样的规范,因此在用-l选项指定链接的库文件名时可以省去lib三个字母,也就是说GCC在对-lfoo进行处理时,会自动去链接名为libfoo.so的文件。

Linux下的库文件分为两大类分别是动态链接库(通常以.so结尾)和静态链接库(通常以.a结尾),两者的差别仅在程序执行时所需的代码是在运行时动态加载的,还是在编译时静态加载的。默认情况下,GCC在链接时优先使用动态链接库,只有当动态链接库不存在时才考虑使用静态链接库,如果需要的话可以在编译时加上-static选项,强制使用静态链接库。例如,如果在/home/xiaowp/lib/目录下有链接时所需要的库文件libfoo.so和libfoo.a,为了让GCC在链接时只用到静态链接库,可以使用下面的命令:

 # gcc foo.c -L /home/xiaowp/lib -static -lfoo -o foo
 
代码优化

代码优化指的是编译器通过分析源代码,找出其中尚未达到最优的部分,然后对其重新进行组合,目的是改善程序的执行性能。GCC提供的代码优化功能非常强大,它通过编译选项-On来控制优化代码的生成,其中n是一个代表优化级别的整数。对于不同版本的GCC来讲,n的取值范围及其对应的优化效果可能并不完全相同,比较典型的范围是从0变化到2或3。

编译时使用选项-O可以告诉GCC同时减小代码的长度和执行时间,其效果等价于-O1。在这一级别上能够进行的优化类型虽然取决于目标处理器,但一般都会包括线程跳转(Thread Jump)和延迟退栈(Deferred Stack Pops)两种优化。选项-O2告诉GCC除了完成所有-O1级别的优化之外,同时还要进行一些额外的调整工作,如处理器指令调度等。选项-O3则除了完成所有-O2级别的优化之外,还包括循环展开和其它一些与处理器特性相关的优化工作。通常来说,数字越大优化的等级越高,同时也就意味着程序的运行速度越快。许多Linux程序员都喜欢使用-O2选项,因为它在优化长度、编译时间和代码大小之间,取得了一个比较理想的平衡点。

下面通过具体实例来感受一下GCC的代码优化功能,所用程序如清单3所示。

清单3:optimize.c

 #include <stdio.h>
 int main(void)
 {
  double counter;
  double result;
  double temp;
  
  for (counter = 0; counter < 2000.0 * 2000.0 * 2000.0 / 20.0 + 2020; counter += (5 – 1) / 4)
  {
    temp = counter / 1979; result = counter;
  }
  printf(“Result is %lf\n”, result); return 0;
 }

首先不加任何优化选项进行编译:

 # gcc -Wall optimize.c -o optimize
 
借助Linux提供的time命令,可以大致统计出该程序在运行时所需要的时间:

 # time ./optimize Result is 400002019.000000 real 0m14.942s user 0m14.940s sys 0m0.000s
 
接下去使用优化选项来对代码进行优化处理:

 # gcc -Wall -O optimize.c -o optimize
 
在同样的条件下再次测试一下运行时间:

 # time ./optimize Result is 400002019.000000 real 0m3.256s user 0m3.240s sys 0m0.000s
 
对比两次执行的输出结果不难看出,程序的性能的确得到了很大幅度的改善,由原来的14秒缩短到了3秒。这个例子是专门针对GCC的优化功能而设计的,因此优化前后程序的执行速度发生了很大的改变。尽管GCC的代码优化功能非常强大,但作为一名优秀的Linux程序员,首先还是要力求能够手工编写出高质量的代码。如果编写的代码简短,并且逻辑性强,编译器就不会做更多的工作,甚至根本用不着优化。

优化虽然能够给程序带来更好的执行性能,但在如下一些场合中应该避免优化代码:

◆ 程序开发的时候 优化等级越高,消耗在编译上的时间就越长,因此在开发的时候最好不要使用优化选项,只有到软件发行或开发结束的时候,才考虑对最终生成的代码进行优化。

◆ 资源受限的时候 一些优化选项会增加可执行代码的体积,如果程序在运行时能够申请到的内存资源非常紧张(如一些实时嵌入式设备),那就不要对代码进行优化,因为由这带来的负面影响可能会产生非常严重的后果。

◆ 跟踪调试的时候 在对代码进行优化的时候,某些代码可能会被删除或改写,或者为了取得更佳的性能而进行重组,从而使跟踪和调试变得异常困难。

调试

一个功能强大的调试器不仅为程序员提供了跟踪程序执行的手段,而且还可以帮助程序员找到解决问题的方法。对于Linux程序员来讲,GDB(GNU Debugger)通过与GCC的配合使用,为基于Linux的软件开发提供了一个完善的调试环境。

默认情况下,GCC在编译时不会将调试符号插入到生成的二进制代码中,因为这样会增加可执行文件的大小。如果需要在编译时生成调试符号信息,可以使用GCC的-g或者-ggdb选项。GCC在产生调试符号时,同样采用了分级的思路,开发人员可以通过在-g选项后附加数字1、2或3来指定在代码中加入调试信息的多少。默认的级别是2(-g2),此时产生的调试信息包括扩展的符号表、行号、局部或外部变量信息。级别3(-g3)包含级别2中的所有调试信息,以及源代码中定义的宏。级别1(-g1)不包含局部变量和与行号有关的调试信息,因此只能够用于回溯跟踪和堆栈转储之用。回溯跟踪指的是监视程序在运行过程中的函数调用历史,堆栈转储则是一种以原始的十六进制格式保存程序执行环境的方法,两者都是经常用到的调试手段。

GCC产生的调试符号具有普遍的适应性,可以被许多调试器加以利用,但如果使用的是GDB,那么还可以通过-ggdb选项在生成的二进制代码中包含GDB专用的调试信息。这种做法的优点是可以方便GDB的调试工作,但缺点是可能导致其它调试器(如DBX)无法进行正常的调试。选项-ggdb能够接受的调试级别和-g是完全一样的,它们对输出的调试符号有着相同的影响。

需要注意的是,使用任何一个调试选项都会使最终生成的二进制文件的大小急剧增加,同时增加程序在执行时的开销,因此调试选项通常仅在软件的开发和调试阶段使用。调试选项对生成代码大小的影响从下面的对比过程中可以看出来:

 # gcc optimize.c -o optimize # ls optimize -l -rwxrwxr-x 1 xiaowp xiaowp 11649 Nov 20 08:53 optimize (未加调试选项) # gcc -g optimize.c -o optimize # ls optimize -l -rwxrwxr-x 1 xiaowp xiaowp 15889 Nov 20 08:54 optimize (加入调试选项)
 
虽然调试选项会增加文件的大小,但事实上Linux中的许多软件在测试版本甚至最终发行版本中仍然使用了调试选项来进行编译,这样做的目的是鼓励用户在发现问题时自己动手解决,是Linux的一个显著特色。

下面还是通过一个具体的实例说明如何利用调试符号来分析错误,所用程序见清单4所示。

清单4:crash.c

 #include <stdio.h>
 int main(void)
 {
  int input =0;
  
  printf(“Input an integer:”);
  scanf(“%d”, input);
  printf(“The integer you input is %d\n”, input);
  return 0;
 }
 
编译并运行上述代码,会产生一个严重的段错误(Segmentation fault)如下:

 # gcc -g crash.c -o crash # ./crash Input an integer:10 Segmentation fault
 
为了更快速地发现错误所在,可以使用GDB进行跟踪调试,方法如下:

 # gdb crash GNU gdb Red Hat Linux (5.3post-0.20021129.18rh) …… (gdb)
 
当GDB提示符出现的时候,表明GDB已经做好准备进行调试了,现在可以通过run命令让程序开始在GDB的监控下运行:

 (gdb) run Starting program: /home/xiaowp/thesis/gcc/code/crash Input an integer:10 Program received signal SIGSEGV, Segmentation fault. 0×4008576b in _IO_vfscanf_internal () from /lib/libc.so.6
 
仔细分析一下GDB给出的输出结果不难看出,程序是由于段错误而导致异常中止的,说明内存操作出了问题,具体发生问题的地方是在调用_IO_vfscanf_internal ( )的时候。为了得到更加有价值的信息,可以使用GDB提供的回溯跟踪命令backtrace,执行结果如下:

 (gdb) backtrace #0 0×4008576b in _IO_vfscanf_internal () from /lib/libc.so.6 #1 0xbffff0c0 in ?? () #2 0×4008e0ba in scanf () from /lib/libc.so.6 #3 0×08048393 in main () at crash.c:11 #4 0×40042917 in __libc_start_main () from /lib/libc.so.6
 
跳过输出结果中的前面三行,从输出结果的第四行中不难看出,GDB已经将错误定位到crash.c中的第11行了。现在仔细检查一下:

 (gdb) frame 3 #3 0×08048393 in main () at crash.c:11 11 scanf(“%d”, input);
 
使用GDB提供的frame命令可以定位到发生错误的代码段,该命令后面跟着的数值可以在backtrace命令输出结果中的行首找到。现在已经发现错误所在了,应该将

 scanf(“%d”, input); 改为 scanf(“%d”, &input);
 
完成后就可以退出GDB了,命令如下:

 (gdb) quit
 
GDB的功能远远不止如此,它还可以单步跟踪程序、检查内存变量和设置断点等。

调试时可能会需要用到编译器产生的中间结果,这时可以使用-save-temps选项,让GCC将预处理代码、汇编代码和目标代码都作为文件保存起来。如果想检查生成的代码是否能够通过手工调整的办法来提高执行性能,在编译过程中生成的中间文件将会很有帮助,具体情况如下:

 # gcc -save-temps foo.c -o foo # ls foo* foo foo.c foo.i foo.s
 
GCC支持的其它调试选项还包括-p和-pg,它们会将剖析(Profiling)信息加入到最终生成的二进制代码中。剖析信息对于找出程序的性能瓶颈很有帮助,是协助Linux程序员开发出高性能程序的有力工具。在编译时加入-p选项会在生成的代码中加入通用剖析工具(Prof)能够识别的统计信息,而-pg选项则生成只有GNU剖析工具(Gprof)才能识别的统计信息。

最后提醒一点,虽然GCC允许在优化的同时加入调试符号信息,但优化后的代码对于调试本身而言将是一个很大的挑战。代码在经过优化之后,在源程序中声明和使用的变量很可能不再使用,控制流也可能会突然跳转到意外的地方,循环语句有可能因为循环展开而变得到处都有,所有这些对调试来讲都将是一场噩梦。建议在调试的时候最好不使用任何优化选项,只有当程序在最终发行的时候才考虑对其进行优化。

上次的培训园地中介绍了GCC的编译过程、警告提示功能、库依赖、代码优化和程序调试六个方面的内容。这期是最后的一部分内容。

加速

在将源代码变成可执行文件的过程中,需要经过许多中间步骤,包含预处理、编译、汇编和连接。这些过程实际上是由不同的程序负责完成的。大多数情况下GCC可以为Linux程序员完成所有的后台工作,自动调用相应程序进行处理。

这样做有一个很明显的缺点,就是GCC在处理每一个源文件时,最终都需要生成好几个临时文件才能完成相应的工作,从而无形中导致处理速度变慢。例如,GCC在处理一个源文件时,可能需要一个临时文件来保存预处理的输出、一个临时文件来保存编译器的输出、一个临时文件来保存汇编器的输出,而读写这些临时文件显然需要耗费一定的时间。当软件项目变得非常庞大的时候,花费在这上面的代价可能会变得很沉重。

解决的办法是,使用Linux提供的一种更加高效的通信方式—管道。它可以用来同时连接两个程序,其中一个程序的输出将被直接作为另一个程序的输入,这样就可以避免使用临时文件,但编译时却需要消耗更多的内存。

在编译过程中使用管道是由GCC的-pipe选项决定的。下面的这条命令就是借助GCC的管道功能来提高编译速度的:

 # gcc -pipe foo.c -o foo
 
在编译小型工程时使用管道,编译时间上的差异可能还不是很明显,但在源代码非常多的大型工程中,差异将变得非常明显。

文件扩展名

在使用GCC的过程中,用户对一些常用的扩展名一定要熟悉,并知道其含义。为了方便大家学习使用GCC,在此将这些扩展名罗列如下:

.c C原始程序;

.C C++原始程序;

.cc C++原始程序;

.cxx C++原始程序;

.m Objective-C原始程序;

.i 已经过预处理的C原始程序;

.ii 已经过预处理之C++原始程序;

.s 组合语言原始程序;

.S 组合语言原始程序;

.h 预处理文件(标头文件);

.o 目标文件;

.a 存档文件。

GCC常用选项

GCC作为Linux下C/C++重要的编译环境,功能强大,编译选项繁多。为了方便大家日后编译方便,在此将常用的选项及说明罗列出来如下:

-c 通知GCC取消链接步骤,即编译源码并在最后生成目标文件;

-Dmacro 定义指定的宏,使它能够通过源码中的#ifdef进行检验;

-E 不经过编译预处理程序的输出而输送至标准输出;

-g3 获得有关调试程序的详细信息,它不能与-o选项联合使用;

-Idirectory 在包含文件搜索路径的起点处添加指定目录;

-llibrary 提示链接程序在创建最终可执行文件时包含指定的库;

-O、-O2、-O3 将优化状态打开,该选项不能与-g选项联合使用;

-S 要求编译程序生成来自源代码的汇编程序输出;

-v 启动所有警报;

-Wall 在发生警报时取消编译操作,即将警报看作是错误;

-Werror 在发生警报时取消编译操作,即把报警当作是错误;

-w 禁止所有的报警。

小结

GCC是在Linux下开发程序时必须掌握的工具之一。本文对GCC做了一个简要的介绍,主要讲述了如何使用GCC编译程序、产生警告信息、调试程序和加快GCC的编译速度。对所有希望早日跨入Linux开发者行列的人来说,GCC就是成为一名优秀的Linux程序员的起跑线。