C之诡谲(转载自优快云)

聚焦112Gb/s SerDes芯片的AN/LT端口自协商和链路学习,评估验证高速链路的信号质量并分析调优(400/800G高速以太网互联接口,AI加速卡网络RDMA性能测试,交换背板接口性能评估) 在112G SerDes高速通道下的400G/800G高速以太网端口下,如何保障通信速率,验证SerDes的数据完整性和链路余量,自动协商(AN)和链路训练(LT)功能显得尤其重要,其在DAC、ACC、AEC铜缆连接或高速背板通信上,自动协商(AN)和链路训练(LT)的工作与否将决定高速链路传输质量的优劣。本文将系统的讨论介绍自动协商(AN)和链路训练(LT)的测试流程,其可以独立使用,也可以与teledynelecroy SierraNet M1288配合使用。 阅读详情

发信人: ccrazy (我为C狂), 信区: CProgramming
标  题: C之诡谲(转载自优快云)zz
发信站: BBS 水木清华站 (Tue Jan 11 23:03:19 2005), 站内

发信人: Taliux (优游一笑), 信区: C_and_CPP
标  题: C之诡谲(转载自优快云)
发信站: 哈工大紫丁香 (2003年06月19日13:58:23 星期四), 站内信件

从研究生二年纪开始学习计算机也差不多两年了,一路走来,有很多的收获,也有不少
的遗憾,现在正好有一段闲暇,就想对走过的路留下一些足迹,回忆。每个人都有自己不同
的人生,说到这里,就是程序人生了,歌德在《浮士德》中说过:“如果不曾在悲哀中咀
嚼过面包,不曾在哭泣中等待过明天,这样的人就不知道你——天的力量。”所以我想记
下一些带给我悲哀,带给我哭泣的程序人生。其实学习计算机的基础课程是非常重要的,
离散数学,编译原理,操作系统,形式语言……,如果你认真走过了这些路,在以后的日
子你会发现你的路会越走越宽,以前的努力和汗水会不断的给你灵感,给你支持,给你前
进的武器和勇气。你会发现以后取得的很多成就,不过是朝花夕拾而已!
对于程序语言我喜欢的是C++,它能带给你别的语言无法给予你的无上的智力快感,当然也
会给你一门语言所能给你的魔鬼般的折磨。其实Java,C#,Python语言也非常的不错,我
也极为喜欢。它们都是非常成功的语言,我从来就不愿意做某一种语言的盲目信仰者,每
种语言都有它成功的地方,失败的地方,都有它适合的地方,不如意的地方。所以每一次
看到评价语言的文章,我看看,但从来不会发言。
C++的前世是C,而且C所留下的神秘以及精简在C++中是青出于蓝而胜于蓝!C所带给人的困
惑以及灵活太多,即使一个有几年经验的高段C程序员仍然有可能在C语言的小水沟里翻船
。不过其实C语言真的不难,下面我想指出C语言中最神秘而又诡谲多变的四个地方,它们
也继续在C++语言中变幻莫测。
指针,数组,类型的识别,参数可变的函数。
一.指针。
它的本质是地址的类型。在许多语言中根本就没有这个概念。但是它却正是C灵活,高效,
在面向过程的时代所向披靡的原因所在。因为C的内存模型基本上对应了现在von Neumann
(冯·诺伊曼)计算机的机器模型,很好的达到了对机器的映射。不过有些人似乎永远也
不能理解指针【注1】。
注1:Joel Spolsky就是这样认为的,他认为对指针的理解是一种aptitude,不是通过训练
就可以达到的。http://www.joelonsoftware.com/printerFriendly/articles/fog000000

0073.html
指针可以指向值、数组、函数,当然它也可以作为值使用。
看下面的几个例子:
int* p;//p是一个指针,指向一个整数
int** p;//p是一个指针,它指向第二个指针,然后指向一个整数
int (*pa)[3];//pa是一个指针,指向一个拥有3个整数的数组
int (*pf)();//pf是一个指向函数的指针,这个函数返回一个整数
后面第四节我会详细讲解标识符(identifier)类型的识别。
1.指针本身的类型是什么?
先看下面的例子:int a;//a的类型是什么?
对,把a去掉就可以了。因此上面的4个声明语句中的指针本身的类型为:
int* 
int**
int (*)[3]
int (*)()
它们都是复合类型,也就是类型与类型结合而成的类型。意义分别如下:
point to int(指向一个整数的指针)
pointer to pointer to int(指向一个指向整数的指针的指针)
pointer to array of 3 ints(指向一个拥有三个整数的数组的指针)
pointer to function of parameter is void and return value is int (指向一个函数
的指针,这个函数参数为空,返回值为整数)
2.指针所指物的类型是什么?
很简单,指针本身的类型去掉 “*”号就可以了,分别如下:
int
int*
int ()[3]
int ()()
3和4有点怪,不是吗?请擦亮你的眼睛,在那个用来把“*”号包住的“()”是多余的,所
以:
int ()[3]就是int [3](一个拥有三个整数的数组)
int ()()就是int ()(一个函数,参数为空,返回值为整数)【注2】
注2:一个小小的提醒,第二个“()”是一个运算符,名字叫函数调用运算符(function 
call operator)。
3.指针的算术运算。
请再次记住:指针不是一个简单的类型,它是一个和指针所指物的类型复合的类型。因此
,它的算术运算与之(指针所指物的类型)密切相关。
int a[8];
int* p = a;
int* q = p + 3;
p++;
指针的加减并不是指针本身的二进制表示加减,要记住,指针是一个元素的地址,它每加
一次,就指向下一个元素。所以:
int* q = p + 3;//q指向从p开始的第三个整数。
p++;//p指向下一个整数。
double* pd;
……//某些计算之后
double* pother = pd – 2;//pother指向从pd倒数第二个double数。
4.指针本身的大小。
在一个现代典型的32位机器上【注3】,机器的内存模型大概是这样的,想象一下,内存空
间就像一个连续的房间群。每一个房间的大小是一个字节(一般是二进制8位)。有些东西
大小是一个字节(比如char),一个房间就把它给安置了;但有些东西大小是几个字节(
比如double就是8个字节,int就是4个字节,我说的是典型的32位),所以它就需要几个房
间才能安置。
注3:什么叫32位?就是机器CPU一次处理的数据宽度是32位,机器的寄存器容量是32位,
机器的数据,内存地址总线是32位。当然还有一些细节,但大致就是这样。16位,64位,
128位可以以此类推。
这些房间都应该有编号(也就是地址),32位的机器内存地址空间当然也是32位,所以房
间的每一个编号都用32位的二进制数来编码【注4】。请记住指针也可以作为值使用,作为
值的时候,它也必须被安置在房间中(存储在内存中),那么指向一个值的指针需要一个
地址大小来存储,即32位,4个字节,4个房间来存储。
注4:在我们平常用到的32位机器上,绝少有将32位真实内存地址空间全用完的(232 = 
4G),即使是服务器也不例外。现代的操作系统一般会实现32位的虚拟地址空间,这样可
以方便运用程序的编制。关于虚拟地址(线性地址)和真实地址的区别以及实现,可以参
考《Linux源代码情景分析》的第二章存储管理,在互联网上关于这个主题的文章汗牛充栋
,你也可以google一下。
但请注意,在C++中指向对象成员的指针(pointer to member data or member function
)的大小不一定是4个字节。为此我专门编制了一些程序,发现在我的两个编译器(VC7.1
.3088和Dev-C++4.9.7.0)上,指向对象成员的指针的大小没有定值,但都是4的倍数。不
同的编译器还有不同的值。对于一般的普通类(class),指向对象成员的指针大小一般为
4,但在引入多重虚拟继承以及虚拟函数的时候,指向对象成员的指针会增大,不论是指向
成员数据,还是成员函数。【注5】。
注5:在Andrei Alexandrescu的《Modern C++ Design》的5.13节Page124中提到,成员函
数指针实际上是带标记的(tagged)unions,它们可以对付多重虚拟继承以及虚拟函数,
书上说成员函数指针大小是16,但我的实践告诉我这个结果不对,而且具体编译器实现也
不同。一直很想看看GCC的源代码,但由于旁骛太多,而且心不静,本身难度也比较高(这
个倒是不害怕^_^),只有留待以后了。
还有一点,对一个类的static member来说,指向它的指针只是普通的函数指针,不是poi
nter to class member,所以它的大小是4。
5.指针运算符&和*
它们是一对相反的操作,&取得一个东西的地址(也就是指针),*得到一个地址里放的东
西。这个东西可以是值(对象)、函数、数组、类成员(class member)。
其实很简单,房间里面居住着一个人,&操作只能针对人,取得房间号码;
*操作只能针对房间,取得房间里的人。
参照指针本身的类型以及指针所指物的类型很好理解。
小结:其实你只要真正理解了1,2,就相当于掌握了指针的牛鼻子。后面的就不难了,指
针的各种变化和C语言中其它普通类型的变化都差不多(比如各种转型)。
二.数组。
在C语言中,对于数组你只需要理解三件事。
1.C语言中有且只有一维数组。
所谓的n维数组只是一个称呼,一种方便的记法,都是使用一维数组来仿真的。
C语言中数组的元素可以是任何类型的东西,特别的是数组作为元素也可以。所以int a[3
][4][5]就应该这样理解:a是一个拥有3个元素的数组,其中每个元素是一个拥有4个元素
的数组,进一步其中每个元素是拥有5个整数元素的数组。
是不是很简单!数组a的内存模型你应该很容易就想出来了,不是吗?:)
2.数组的元素个数,必须作为整数常量在编译阶段就求出来。
int i;
int a[i];//不合法,编译不会通过。
也许有人会奇怪char str[] = “test”;没有指定元素个数为什么也能通过,因为编译器
可以根据后面的初始化字符串在编译阶段求出来,
不信你试试这个:int a[];
编译器无法推断,所以会判错说“array size missing in a”之类的信息。不过在最新的
C99标准中实现了变长数组【注6】
注6:如果你是一个好奇心很强烈的人,就像我一样,那么可以查看C99标准6.7.5.2。
3.对于数组,可以获得数组第一个(即下标为0)元素的地址(也就是指针),从数组名
获得。
比如int a[5]; int* p = a;这里p就得到了数组元素a[0]的地址。
其余对于数组的各种操作,其实都是对于指针的相应操作。比如a[3]其实就是*(a+3)的简
单写法,由于*(a+3)==*(3+a),所以在某些程序的代码中你会看到类似3[a]的这种奇怪表
达式,现在你知道了,它就是a[3]的别名。还有一种奇怪的表达式类似a[-1],现在你也明
白了,它就是*(a-1)【注7】。
注7:你肯定是一个很负责任的人,而且也知道自己到底在干什么。你难道不是吗?:)所
以你一定也知道,做一件事是要付出成本的,当然也应该获得多于成本的回报。
我很喜欢经济学,经济学的一个基础就是做什么事情都是要花成本的,即使你什么事情也
不做。时间成本,金钱成本,机会成本,健康成本……可以这样说,经济学的根本目的就
是用最小的成本获得最大的回报。
所以我们在自己的程序中最好避免这种邪恶的写法,不要让自己一时的智力过剩带来以后
自己和他人长时间的痛苦。用韦小宝的一句话来说:“赔本的生意老子是不干的!”
但是对邪恶的了解是非常必要的,这样当我们真正遇到邪恶的时候,可以免受它对心灵的
困扰!
对于指向同一个数组不同元素的指针,它们可以做减法,比如int* p = q+i;p-q的结果就
是这两个指针之间的元素个数。i可以是负数。但是请记住:对指向不同的数组元素的指针
,这样的做法是无用而且邪恶的!
对于所谓的n维数组,比如int a[2][3];你可以得到数组第一个元素的地址a和它的大小。
*(a+0)(也即a[0]或者*a)就是第一个元素,它又是一个数组int[3],继续取得它的第一
个元素,*(*(a+0)+0)(也即a[0][0]或者*(*a)),也即第一个整数(第一行第一列的第一
个整数)。如果采用这种表达式,就非常的笨拙,所以a[0][0]记法上的简便就非常的有用
了!简单明了!
对于数组,你只能取用在数组有效范围内的元素和元素地址,不过最后一个元素的下一个
元素的地址是个例外。它可以被用来方便数组的各种计算,特别是比较运算。但显然,它
所指向的内容是不能拿来使用和改变的!
关于数组本身大概就这么多,下面简要说一下数组和指针的关系。它们的关系非常暧昧,
有时候可以交替使用。
比如 int main(int args, char* argv[])中,其实参数列表中的char* argv[]就是char*
* argv的另一种写法。因为在C语言中,一个数组是不能作为函数引数(argument)【注8
】直接传递的。因为那样非常的损失效率,而这点违背了C语言设计时的基本理念——作为
一门高效的系统设计语言。
注8:这里我没有使用函数实参这个大陆术语,而是运用了台湾术语,它们都是argument这
个英文术语的翻译,但在很多地方中文的实参用的并不恰当,非常的勉强,而引数表示被
引用的数,很形象,也很好理解。很快你就可以像我一样适应引数而不是实参。
dereferance,也就是*运算符操作。我也用的是提领,而不是解引用。
我认为你一定智勇双全:既有宽容的智慧,也有面对新事物的勇气!你不愿意承认吗?:
)
所以在函数参数列表(parameter list)中的数组形式的参数声明,只是为了方便程序员
的阅读!比如上面的char* argv[]就可以很容易的想到是对一个char*字符串数组进行操作
,其实质是传递的char*字符串数组的首元素的地址(指针)。其它的元素当然可以由这个
指针的加法间接提领(dereferance)【参考注8】得到!从而也就间接得到了整个数组。

但是数组和指针还是有区别的,比如在一个文件中有下面的定义:
char myname[] = “wuaihua”;
而在另一个文件中有下列声明:
extern char* myname;
它们互相是并不认识的,尽管你的本义是这样希望的。
它们对内存空间的使用方式不同【注9】。
对于char myname[] = “wuaihua”如下
myname
w
 u
 a
 i
 h
 u
 a
 /0
 
对于char* myname;如下表
myname
 
 
/|/
w
 u
 a
 i
 h
 u
 a
 /0
 
注9:可以参考Andrew Konig的《C陷阱与缺陷》4.5节。
改变的方法就是使它们一致就可以了。
char myname[] = “wuaihua”;
extern char myname[];
或者
char* myname = “wuaihua”;//C++中最好换成const char* myname = “wuaihua”。
extern char* myname;

三.类型的识别。
基本类型的识别非常简单:
int a;//a的类型是a
char* p;//p的类型是char*
……
那么请你看看下面几个:
int* (*a[5])(int, char*);                //#1
void (*b[10]) (void (*)());             //#2
doube(*)() (*pa)[9];                  //#3
如果你是第一次看到这种类型声明的时候,我想肯定跟我的感觉一样,就如晴天霹雳,五
雷轰顶,头昏目眩,一头张牙舞爪的狰狞怪兽扑面而来。
不要紧(Take it easy)!我们慢慢来收拾这几个面目可憎的纸老虎!
1.C语言中函数声明和数组声明。
函数声明一般是这样int fun(int,double);对应函数指针(pointer to function)的声明
是这样:
int (*pf)(int,double),你必须习惯。可以这样使用:
pf = &fun;//赋值(assignment)操作
(*pf)(5, 8.9);//函数调用操作
也请注意,C语言本身提供了一种简写方式如下:
pf = fun;// 赋值(assignment)操作
pf(5, 8.9);// 函数调用操作
不过我本人不是很喜欢这种简写,它对初学者带来了比较多的迷惑。
数组声明一般是这样int a[5];对于数组指针(pointer to array)的声明是这样:
int (*pa)[5]; 你也必须习惯。可以这样使用:
pa = &a;// 赋值(assignment)操作
int i = (*pa)[2]//将a[2]赋值给i;
 
2.有了上面的基础,我们就可以对付开头的三只纸老虎了!:)
这个时候你需要复习一下各种运算符的优先顺序和结合顺序了,顺便找本书看看就够了。

#1:int* (*a[5])(int, char*);
首先看到标识符名a,“[]”优先级大于“*”,a与“[5]”先结合。所以a是一个数组,这
个数组有5个元素,每一个元素都是一个指针,指针指向“(int, char*)”,对,指向一个
函数,函数参数是“int, char*”,返回值是“int*”。完毕,我们干掉了第一个纸老虎
。:)
#2:void (*b[10]) (void (*)());
b是一个数组,这个数组有10个元素,每一个元素都是一个指针,指针指向一个函数,函数
参数是“void (*)()”【注10】,返回值是“void”。完毕!
注10:这个参数又是一个指针,指向一个函数,函数参数为空,返回值是“void”。
#3. doube(*)() (*pa)[9];  
pa是一个指针,指针指向一个数组,这个数组有9个元素,每一个元素都是“doube(*)()”
【也即一个指针,指向一个函数,函数参数为空,返回值是“double”】。
现在是不是觉得要认识它们是易如反掌,工欲善其事,必先利其器!我们对这种表达方式
熟悉之后,就可以用“typedef”来简化这种类型声明。
#1:int* (*a[5])(int, char*);
typedef int* (*PF)(int, char*);//PF是一个类型别名【注11】。
PF a[5];//跟int* (*a[5])(int, char*);的效果一样!
注11:很多初学者只知道typedef char* pchar;但是对于typedef的其它用法不太了解。
Stephen Blaha对typedef用法做过一个总结:“建立一个类型别名的方法很简单,在传统
的变量声明表达式里用类型名替代变量名,然后把关键字typedef加在该语句的开头”。可
以参看《程序员》杂志2001.3期《C++高手技巧20招》。 
#2:void (*b[10]) (void (*)());
typedef void (*pfv)();
typedef void (*pf_taking_pfv)(pfv);
pf_taking_pfv b[10]; //跟void (*b[10]) (void (*)());的效果一样!
#3. doube(*)() (*pa)[9];  
typedef double(*PF)();
typedef PF (*PA)[9];
PA pa; //跟doube(*)() (*pa)[9];的效果一样!
 
3.const和volatile在类型声明中的位置
在这里我只说const,volatile是一样的【注12】!
注12:顾名思义,volatile修饰的量就是很容易变化,不稳定的量,它可能被其它线程,
操作系统,硬件等等在未知的时间改变,所以它被存储在内存中,每次取用它的时候都只
能在内存中去读取,它不能被编译器优化放在内部寄存器中。
类型声明中const用来修饰一个常量,我们一般这样使用:const在前面
const int;//int是const
const char*;//char是const
char* const;//*(指针)是const
const char* const;//char和*都是const
对初学者,const char*;和 char* const;是容易混淆的。这需要时间的历练让你习惯它。

上面的声明有一个对等的写法:const在后面
int const;//int是const
char const*;//char是const
char* const;//*(指针)是const
char const* const;//char和*都是const
第一次你可能不会习惯,但新事物如果是好的,我们为什么要拒绝它呢?:)const在后面
有两个好处:
A.              const所修饰的类型是正好在它前面的那一个。如果这个好处还不能让
你动心的话,那请看下一个!
B.               我们很多时候会用到typedef的类型别名定义。比如typedef char* p
char,如果用const来修饰的话,当const在前面的时候,就是const pchar,你会以为它就
是const char* ,但是你错了,它的真实含义是char * const 。是不是让你大吃一惊!但如果你采用const在后面的写法,意义就怎么也不会变,不信你试试!
不过,在真实项目中的命名一致性更重要。你应该在两种情况下都能适应,并能自如的转
换,公司习惯,商业利润不论在什么时候都应该优先考虑!不过在开始一个新项目的时候
,你可以考虑优先使用const在后面的习惯用法。
 
四.参数可变的函数
C语言中有一种很奇怪的参数“…”,它主要用在引数(argument)个数不定的函数中,最
常见的就是printf函数。
printf(“Enjoy yourself everyday!/n”);
printf(“The value is %d!/n”, value);
……
你想过它是怎么实现的吗?
1.      printf为什么叫printf?
不管是看什么,我总是一个喜欢刨根问底的人,对事物的源有一种特殊的癖好,一段典故
,一个成语,一句行话,我最喜欢的就是找到它的来历,和当时的意境,一个外文翻译过
来的术语,最低要求我会尽力去找到它原本的外文术语。特别是一个字的命名来历,我一
向是非常在意的,中国有句古话:“名不正,则言不顺。”printf中的f就是format的意思
,即按格式打印【注13】。
注13:其实还有很多函数,很多变量,很多命名在各种语言中都是非常讲究的,你如果细
心观察追溯,一定有很多乐趣和满足,比如哈希表为什么叫hashtable而不叫hashlist?在
C++的SGI STL实现中有一个专门用于递增的函数iota(不是itoa),为什么叫这个奇怪的
名字,你想过吗?看文章我不喜欢意犹未尽,己所不欲,勿施于人,所以我把这两个答案
告诉你:
(1)table与list做为表讲的区别:
table:
-------|--------------------|-------
 item1 |    kadkglasgaldfgl | jkdsfh
-------|--------------------|-------
 item2 |    kjdszhahlka     | xcvz
-------|--------------------|-------
list:
****
***
*******
*****
That's the difference!
如果你还是不明白,可以去看一下hash是如何实现的!
(2)The name iota is taken from the programming language APL.
而APL语言主要是做数学计算的,在数学中有很多公式会借用希腊字母,
希腊字母表中有这样一个字母,大写为Ι,小写为ι,
它的英文拼写正好是iota,这个字母在θ(theta)和κ(kappa)之间!
你可以看看http://www.wikipedia.org/wiki/APL_programming_language
下面有一段是这样的:
APL is renowned for using a set of non-ASCII symbols that are an extension of 
traditional arithmetic and algebraic notation. These cryptic symbols, some hav
e joked, make it possible to construct an entire air traffic control system in
 two lines of code. Because of its condensed nature and non-standard character
s, APL has sometimes been termed a "write-only language", and reading an APL p
rogram can feel like decoding an alien tongue. Because of the unusual characte
r-set, many programmers used special APL keyboards in the production of APL co
de. Nowadays there are various ways to write APL code using only ASCII charact
ers.
在C++中有函数重载(overload)可以用来区别不同函数参数的调用,但它还是不能表示任
意数量的函数参数。
在标准C语言中定义了一个头文件<stdarg.h>专门用来对付可变参数列表,它包含了一组宏
,和一个va_list的typedef声明。一个典型实现如下【注14】:
typedef char* va_list;
#define va_start(list) list = (char*)&va_alist
#define va_end(list)
#define va_arg(list, mode)/
    ((mode*) (list += sizeof(mode)))[-1]
注14:你可以查看C99标准7.15节获得详细而权威的说明。也可以参考Andrew Konig的《C
陷阱与缺陷》的附录A。
ANSI C还提供了vprintf函数,它和对应的printf函数行为方式上完全相同,只不过用va_
list替换了格式字符串后的参数序列。至于它是如何实现的,你在认真读完《The C Prog
ramming Language》后,我相信你一定可以do it yourself!
使用这些工具,我们就可以实现自己的可变参数函数,比如实现一个系统化的错误处理函
数error。它和printf函数的使用差不多。只不过将stream重新定向到stderr。在这里我借
鉴了《C陷阱与缺陷》的附录A的例子。
实现如下:
#include <stdio.h>
#include <stdarg.h>
void error(char* format, …)
{
      va_list ap;
      va_start(ap, format);
      fprintf(stderr, “error: “);
      vfprintf(stderr, format, ap);
      va_end(ap);
      fprintf(stderr, “/n”);
      exit(1);
}
你还可以自己实现printf:
#include <stdarg.h>
int printf(char* format, …)
{
      va_list ap;
      va_start(ap, format);
      int n = vprintf(format, ap);
      va_end(ap);
      return n;
}
我还专门找到了VC7.1的头文件<stdarg.h>看了一下,发现各个宏的具体实现还是有区别的
,跟很多预处理(preprocessor)相关。其中va_list就不一定是char*的别名。
typedef struct {
        char *a0;       /* pointer to first homed integer argument */
        int offset;     /* byte offset of next parameter */
} va_list;
其它的定义类似。
 
经常在Windows进行系统编程的人一定知道函数调用有好几种不同的形式,比如__stdcall
,__pascal,__cdecl。在Windows下_stdcall,__pascal是一样的,所以我只说一下__st
dcall和__cdecl的区别。
(1)__stdcall表示调用端负责被调用函数引数的压栈和出栈。函数参数个数一定的函数
都是这种调用形式。
例如:int fun(char c, double d),我们在main函数中调用它,这个函数就只管本身函数
体的运行,参数怎么来的,怎么去的,它一概不管。自然有main负责。不过,不同的编译
器的实现可能将参数从右向左压栈,也可能从左向右压栈,这个顺序我们是不能加于利用
的【注15】。
注15:你可以在Herb Sutter的《More Exceptional C++》中的条款20:An Unmanaged Po
inter Problem, Part 1:Parameter Evaluation找到相关的细节论述。
(2)__cdecl表示被调用函数自身负责函数引数的压栈和出栈。参数参数可变的函数采用
的是这种调用形式。
为什么这种函数要采用不同于前面的调用形式呢?那是因为__stdcall调用形式对它没有作
用,调用端根本就无法知道被调用函数的引数个数,它怎么可能正确工作?所以这种调用
方式是必须的,不过由于参数参数可变的函数本身不多,所以用的地方比较少。
对于这两种方式,你可以编制一些简单的程序,如何反汇编,在汇编代码下面你就可以看
到实际的区别,很好理解的!
重载函数有很多匹配(match)规则调用。参数为“…”的函数是匹配最低的,这一点在A
ndrei Alexandrescu的惊才绝艳之作《Modern C++ Design》中就有用到,参看Page34-35
,2.7“编译期间侦测可转换性和继承性”。
 
后记:
C语言的细节肯定不会只有这么多,但是这几个出现的比较频繁,而且在C语言中也是很重
要的几个语言特征。如果把这几个细节彻底弄清楚了,C语言本身的神秘就不会太多了。
C语言本身就像一把异常锋利的剪刀,你可以用它做出非常精致优雅的艺术品,也可以剪出
一些乱七八糟的废纸片。能够将一件武器用到出神入化那是需要时间的,需要多长时间?
不多,请你拿出一万个小时来,英国Exter大学心理学教授麦克.侯威专门研究神童和天才
,他的结论很有意思:“一般人以为天才是自然而生、流畅而不受阻的闪亮才华,其实,
天才也必须耗费至少十年光阴来学习他们的特殊技能,绝无例外。要成为专家,需要拥有
顽固的个性和坚持的能力……每一行的专业人士,都投注大量心血,培养自己的专业才能
。”【注16】
注16:台湾女作家、电视节目主持人吴淡如《拿出一万个小时来》。《读者》2003.1期。
“不用太努力,只要持续下去。想拥有一辈子的专长或兴趣,就像一个人跑马拉松赛一样
,最重要的是跑完,而不是前头跑得有多快。”
推荐两本书:
K&R的《The C Programming language》,Second Edition。
Andrew Konig的《C陷阱与缺陷》。本文从中引用了好几个例子,一本高段程序员的经验之
谈。
但是对纯粹的初学者不太合适,如果你有一点程序设计的基础知识,花一个月的时间好好
看看这两本书,C语言本身就不用再花更多的精力了。
吴桐写于2003.5.26

--
※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: 218.9.121.210]
 

--
不用C++,不学Java,
用C创造我们的程序世界。


※ 修改:·cjrf 于 Jan 19 00:49:53 修改本文·[FROM: 211.71.15.*]
※ 来源:·BBS 水木清华站 smth.org·[FROM: 211.71.15.*]
 

Yocto开发讲解系列 - 总目录 Yocto开发讲解系列总纲Yocto开发专栏前言 阅读详情

相关推荐

模拟CMOS 第2章 MOS器件物理基础

模拟CMOS 第2章 MOS器件物理基础 文章目录模拟CMOS 第2章 MOS器件物理基础2.1 基本概念2.1.1 MOSEFT开关2.1.2 MOSEFT结构2.1.3 MOS符号2.2 MOS的I/V特性2.2.1 阈值电压2.2.2 I/V特性的推导2.3 二级效应2.4 MOS器件模型2.4.1 MOS器件版图2.4.2 MOS器件电容2.4.3 MOS小信号模型2.4.4 MOS SPICE模型2.4.5 NMOS与PMOS器件的比较2.4.6 长沟道器件与短沟道器件的比较附录A 用作电容器MO

椰子奶糖的博客 5200

C之诡谲

从研究生二年纪开始学习计算机也差不多两年了,一路走来,有很多的收获,也有不少  的遗憾,现在正好有一段闲暇,就想对走过的路留下一些足迹,回忆。每个人都有自己不同  的人生,说到这里,就是程序人生了,歌德在《浮士德》中说过:“如果不曾在悲哀中咀  嚼过面包,不曾在哭泣中等待过明天,这样的人就不知道你——天的力量。”所以我想记  下一些带给我悲哀,带给我哭泣的程序人生。其实学习计算机的基础课程是非常重

经天纬地 981

IMU数据处理避坑指南:从理论到实践的加速度计重力消除方案

本文详细解析了IMU数据处理中的重力消除技术,从理论基础到实践应用,涵盖了加速度计和陀螺仪的核心问题。通过ROS2实战案例,介绍了静态标定与动态校准的技术对比,以及多传感器融合的进阶策略,帮助开发者避免常见陷阱,提升IMU数据处理的精度和效率。

purple的博客 741

C++语言风格流变史

<!--google_ad_client = "pub-5777883355344765";google_ad_width = 120;google_ad_height = 240;google_ad_format = "120x240_as";google_ad_type = "text";google_ad_channel ="";google_color_

MPU的专栏 1314

【系统分析师】2026年上半年真题:案例分析-答案及详解(回忆版)

案例一聚焦因果分析法(鱼骨图)的应用,分析企业订单履约问题。问题1阐述因果分析法定义及常见方法(鱼骨图、5个为什么等);问题2将8个原因归类为人员、方法、合同等5类;问题3筛选适合鱼骨图的场景(如系统响应慢、服务器故障等),强调其适用于复杂根因分析。 案例二对比嵌入式系统中Linux与RTOS的差异(实时性、资源占用等),结合智能监控终端需求推荐Linux+四核ARM方案(高算力、性价比),并列选Linux的5大优势(生态成熟、多任务支持等)。 案例三设计互联网医院系统用例图(患者、医生等参与

数据知道的博客 1353

YOLOv11改进策略【模型轻量化】| 替换骨干网络为 GhostNet V3 2024华为的重参数轻量化模型

一、本文介绍 本文记录的是基于 GhostNetV3 的 YOLOv11 轻量化改进方法研究。GhostNetV3的轻量模块采用重参数化方法,训练时为深度可分离卷积和1×1卷积添加线性并行分支,推理时通过逆重参数化移除分支、折叠操作,能够在不增加推理成本的同时提高性能,从而实现YOLOv11的轻量化改进。 专栏目录:YOLOv11改进目录一览 | 涉及卷积层、轻量化、注意力、损失函数、Backbone、SPPF、Neck、检测头等全方位改进 专栏地址:YOLOv11改进专栏——以发表论文的角度,快速

Limiiiing的博客 1845

Windows和Mac OS

转自:http://xiongzhend.blog.163.com/blog/static/6409850120081131057589/我为什么偏爱苹果电脑和Mac OS X 作为一个经常使用苹果电脑,并且在Mac OS X上做开发的人,我一直想找个机会出来说一下,我为什么偏爱

StudyRecord的专栏 2031

【系统架构设计师】2025年下半年真题论文回忆版及写作要点

本文摘要围绕云原生数据库、性能测试、无服务器架构和秒杀场景等关键技术进行论述。云原生数据库通过容器化、微服务和自动化运维实现弹性扩展;性能测试需要规划测试场景并关注响应时间等关键指标;无服务器架构以事件驱动为核心,简化开发流程;秒杀场景需组合运用扩容、缓存、限流等技术应对高并发。这些技术方案为构建现代高可用系统提供了重要参考。

数据知道的博客 6157

我为什么偏爱苹果电脑和Mac OS X

作为一个经常使用苹果电脑,并且在Mac OS X上做开发的人,我一直想找个机会出来说一下,我为什么偏爱苹果电脑和Mac OS X。 我爱苹果,爱Steve jobs,但同时我也爱微软,崇拜Bill Gates,他是我儿时的偶像。作为一个以写程序为生的人,Windows对我来说一点障碍也没有,基本上我知道如何重装,知道如何处理Windows上的任何问题,一时半会不知道的也能通过搜索引擎知道,我

Jabez的专栏 1210

42、多媒体与虚拟现实在艺术和影视领域的创新应用

本文探讨了多媒体与虚拟现实在艺术和影视领域的创新应用,涵盖三个典型案例:影视与人类学融合项目通过协作模式模糊作者与观众界限,推动文化文献的发展;交互式数字短片《分手》利用基于对象的广播技术探索个性化影视体验;威尼斯双年展瑞士馆4D项目则通过虚拟现实技术实现文化遗产的数字化展示。这些案例展示了技术在叙事、用户体验及文化保护中的深度应用,并展望了未来技术融合、个性化深化与文化遗产传承的发展趋势。

kappa的博客 114

详细解读经典跨模态对齐技术Q-Former代码

介绍BLIP2的博客层出不穷,介绍Q-Former的博客也不少,但是详细解读Q-Former代码的博客很少。这篇博客希望以代码为基础,讲明白Q-Former的原理和核心思想,并详细解读一些值得注意的细节。希望读者们能从这一经典技术中朝花夕拾,得到启发。

沉迷单车的追风少年 2459

重温经典,《魔兽争霸 3:冰封王座》安装包下载指南

如今,无论是想重温经典战役的怀旧玩家,还是渴望体验经典Dota对战的电竞爱好者,都能通过便捷的方式重拾这份乐趣,而优质的下载资源则是开启这段旅程的关键。从“混乱之治”到“冰封王座”的完整战役剧情,更是串联起阿尔萨斯从圣骑士堕落为巫妖王、伊利丹被封印等震撼人心的故事场景,仿佛一部可互动的奇幻史诗,让玩家在战斗中沉浸式感受艾泽拉斯大陆的悲欢离合。配合稳定的对战平台,玩家还能实现低延迟多人联机,无论是与好友组队重现“网吧五连坐”的欢乐时光,还是与全球玩家同台竞技切磋技巧,都能享受流畅的对战体验。

大山运维的博客 2117

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

最大岛屿

最大岛屿 时间限制:1000ms | 内存限制:65535KB 难度:2 描述 神秘的海洋,惊险的探险之路,打捞海底宝藏,激烈的海战,海盗劫富等等。加勒比海盗,你知道吧?杰克船长驾驶着自己的的战船黑珍珠1号要...

dengjing1200的博客 140

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

云有约 | 在斩获“数据金羊毛”的途中,易观造船现行,人称Argo号~

戳蓝字“优快云云计算”关注我们哦!作者:刘晶晶民皆富庶的大唐王朝,唐僧师徒历劫八十一难求取真经;神秘诡谲的古希腊神话,阿格尔英雄经历万千磨难智获金羊毛,均是一路辛苦。真...

优快云云计算 693

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

LeetCode高频题:客栈选择问题,总共有多少种选择住宿的方案,保证晚上可以找到一家最低消费不超过 p 元的咖啡店小聚

1)客栈问题是很经典的题目,贪心,然后统计可以组合的方案 2)贪心的题目就是小根堆加排序解决的,老油条了 3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。 ......

weixin_46838716的博客 497

不想当亿万富翁的程序员不是好老板

【编者按】业界金童、福布斯封面人物、程序员、自主创业者、 科技圈商业奇才、程序员式血汗工厂的缔造者……在风云诡谲的硅谷圈内,Joe Liemandt 身负诸多传奇,是极具神秘色彩的「少年天才式」人物。拒绝采访、避免公开露面、回避对外发声——虽不在江湖但江湖尽是他的传说。2001 年掉出福布斯四百强榜单的他时隔十七年王者归来,更开启了「云薪资」时代,然此番续写的究竟是程序员心中的他年英雄还是今...

优快云资讯 2179

RSA非对称加密算法:从数学原理到HTTPS/SSH实战应用

非对称加密是现代信息安全体系的基石,它通过公钥与私钥的配对,解决了对称加密中密钥分发的核心难题。其安全性建立在特定数学问题的计算复杂性之上,例如大整数质因数分解的困难性。这种机制为数字世界提供了机密性、完整性和身份认证三大核心价值,广泛应用于构建可信的安全通道。在工程实践中,非对称加密最常见的应用场景包括HTTPS/SSL/TLS协议中的密钥交换,以及SSH免密登录的身份认证过程。以RSA算法为例,它通过选择大质数、计算模数和欧拉函数等步骤生成密钥对,并利用模幂运算实现加密解密与数字签名。理解其原理并掌握O

weixin_33912453的博客 362

深挖C++:类和对象(中)

(先进后出)

xys_678的博客 241

C++移动构造函数详解

移动构造函数是C++11引入的一种特殊构造函数,用于从临时对象(右值)"窃取"资源来初始化新对象,而不是进行昂贵的深拷贝。char* data;public:// 移动构造函数:参数是右值引用 &&// 关键:把源对象的资源置空。

拒绝AI文,努力在AI时代写出感动人心的好文章 391

ThunderKittens 深度解析:一个不基于 MLIR 的 C++ 模板 GPU DSL

Triton 是"让更多人能写 GPU kernel",ThunderKittens 是"让会写的人写得更爽"。前者用 MLIR 换易用性,后者用 C++ 模板换可控性。它们不是替代关系,而是覆盖了 GPU 编程光谱的两端。ThunderKittens 的出现,本质上是对"GPU DSL 必须基于 MLIR"这一默认假设的挑战。编译期类型系统本身就可以是一个强大的 DSL 基础设施,不需要中间 IR,不需要黑盒 pass,也能实现 tile 级抽象和零运行时开销。

Maxwell_Newton的博客 339

跟我学c++中级篇—类型定义的方式

本文系统阐述了编程中类型定义的重要性及c++中的实现方式。重点对比了宏定义、typedef与modern using三种机制,指出using在可读性、灵活性和模板支持上的优势,推荐优先使用。同时提醒开发者注意别名滥用、类型歧义、错误信息冗长等问题,强调统一规范与合理设计。最终结论:在现代c++开发中,应以using为主,兼顾简洁性与可维护性,避免陷入命名混乱与编译困境。

fpcc的专栏 70
上一篇: 一个DDOS工具源码
下一篇: 关于IP地址的几个函数
starshift
博客等级 码龄21年 66粉丝 60原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值