存档

‘代码学习’ 分类的存档

一个比lxr更完善的代码在线浏览网站–www.sooset.com

2010年4月14日

http://www.sooset.com
看上去比lxr要好不少,而且有备案。。。。。

zhangxuecheng 代码学习

C语言中数组和指针的区别

2009年9月5日

水文一篇, 胡乱写写.

比较清晰的讲解在<C专家编程>这本书中.

声明(Declaration):

int a[];

extern int *p;

定义(Definitation):

int a[10];

int *p

int *p = a;

引用(Reference):

a[10]=1;

*p=NULL;

几个前提:

1.声明和定义必须完全一致, 否则编译器会报错

2.不声明就直接引用是可以的, 编译器不会报错, 但是引用和定义必须一致, 否则运行时会发生错误

关于声明和定义:

定义就是告诉编译器, 要按照一定的方式来分配内存, 并且给这块内存一个唯一的名字.

而声明只是告诉编译器, 你准备以什么样的方式来定义(分配内存并给它命名), 以后编译器就会按照这个方式去引用(就是访问)这段内存.

如果没有声明, 编译器会按照它的理解去访问这段内存, 对函数或者指针这方面都没有问题, 但是对函数会有些问题, 因为编译器会假设没有声明的函数是int func(…)这样的形式, 即返回值是int型, 参数个数和类型不限, 往往会造成一些返回值为void或者其他类型的函数调用编译不过.

数组和指针在声明与定义方面的问题在于, 你不能定义为数组, 然后声明为指针, 更不能定义为指针, 然后声明为数组, 这是由数组和指针的引用方式不同引起的, 也就是访问内存中数据的方式不同.

当你引用一个数组的元素时, 编译器会从固定的地址开始, 计算出偏移量, 然后得到最终的数据

而访问指针时, 编译器会先得到指针指向的那个地址, 然后从那个地址开始, 计算出偏移量, 然后得到最终的数据

所以当指针和数组的引用发生混乱时, 编译器就可能将一个实际存放数据的地址当成指针, 从而到另外一个地址去取数据, 导致运行时的错误(数组当做指针去引用);或者将一个存放指针的地址当做实际的数据存储区, 从而拿到一个错误的数据(指针当做数组去引用)

也就是你不能在为文件A中定义一个数组, 然后在文件B中将其声明为函数, 反过来也不行.

但是有几处例外

一是数组和指针作为函数的形参时, 它们是等价的, 你可以用void func(char s[]), 也可以用void func(char *s), 因为当函数调用时, 实际传过来的参数是指向实际内存区域的指针, 即编译器无论如何都会作为一个指针去引用参数s, 所以怎么声明都可以. 而数组之所以被作为指针传递, 主要是基于效率的考虑, 如果总是把整个数组的内容在栈上传来传去, 效率损失太大了.

二是对数组引用时, 可以等价为指针, 因为引用一个数组名时, 编译器会把它解释为指向数组第一个元素的指针, 而”[]“操作符是等价于”+”的, 你可以写a[5], 也可以写5[a], 没有任何问题. 所以通常情况下, 对数组的引用是被当做指针来使用的, 前提是编译器正确的将其作为数组处理, 而不能被错误的声明所迷惑.

最后补充一点:任何类型都不能转换为数组,但是指针可以强制转换

zhangxuecheng 代码学习

Google C++ 编程风格指南

2009年8月5日

中文版项目在这里:

http://code.google.com/p/zh-google-styleguide/

英文版在这里:

http://code.google.com/p/google-styleguide/

甚至还有emacs的专门的style:

http://google-styleguide.googlecode.com/svn/trunk/google-c-style.el

译者前言 

Google 经常会发布一些开源项目, 意味着会接受来自其他代码贡献者的代码. 但是如果代码贡献者的编程风格与 Google 的不一致, 会给代码阅读者和其他代码提交这造成不小的困扰. Google 因此发布了这份自己的编程风格, 使所有提交代码的人都能获知 Google 的编程风格.

翻译初衷:
规则的作用就是避免混乱. 但规则本身一定要权威, 有说服力, 并且是理性的. 我们所见过的大部分 编程规范 , 其内容或不够严谨, 或阐述过于简单, 或带有一定的武断性.

Google 保持其一贯的严谨精神, 5 万汉字的 指南 涉及广泛, 论证严密. 我们翻译该系列指南的主因也正是其严谨性. 严谨意味着指南的价值不仅仅局限于它罗列出的规范, 更具参考意义的是它为了列出规范而做的谨慎权衡过程.

指南 不 仅列出你要怎么做, 还告诉你为什么要这么做, 哪些情况下可以不这么做, 以及如何权衡其利弊. 其他团队未必要完全遵照指南亦步亦趋, 如前面所说, 这份指南是 Google 根据自身实际情况打造的, 适用于其主导的开源项目. 其他团队可以参照该指南, 或从中汲取灵感, 建立适合自身实际情况的规范.

我们在翻译的过程中, 收获颇多. 希望本系列 指南 中文版对你同样能有所帮助.

我们翻译时也是尽力保持严谨, 但水平所限, bug 在所难免. 有任何意见或建议, 可与我们取得联系.

中文版和英文版一样, 使用 Artistic License/GPL 开源许可.

中文版修订历史:
  • 2009-06 3.133 : YuleFox 的 1.0 版已经相当完善, 但原版在近一年的时间里, 其规范也发生了一些变化.

    yospaly 与 YuleFox 一拍即合, 以项目的形式来延续中文版 : Google 开源项目风格指南 – 中文版项目 .

    主要变化是同步到 3.133 最新英文版本, 做部分勘误和改善可读性方面的修改, 并改进排版效果. yospaly 重新翻修, YuleFox 做后续评审.

  • 2008-07 1.0 : 出自 YuleFox 的 Blog , 很多地方摘录的也是该版本.

背景 

C++ ++ 是 Google 大部分开源项目的主要编程语言. 正如每个 C++ 程序员都知道的, C++ 有很多强大的特性, 但这种强大不可避免的导致它走向复杂,使代码更容易产生 bug, 难以阅读和维护.

指南 的目的是通过详细阐述 C++ ++ 注意事项来驾驭其复杂性. 这些规则在保证代码易于管理的同时, 高效使用 C++ 的语言特性.

风格 , 亦被称作可读性, 也就是指导 C++ 编程的约定. 使用术语 “风格” 有些用词不当, 因为这些习惯远不止源代码文件格式化这么简单.

使代码易于管理的方法之一是加强代码一致性. 让任何程序员都可以快速读懂你的代码这点非常重要. 保持统一编程风格并遵守约定意味着可以很容易根据 “模式匹配” 规则来推断各种标识符的含义. 创建通用, 必需的习惯用语和模式可以使代码更容易理解. 在一些情况下可能有充分的理由改变某些编程风格, 但我们还是应该遵循一致性原则,尽量不这么做.

指南 的另一个观点是 C++ ++ 特性的臃肿. C++ 是一门包含大量高级特性的庞大语言. 某些情况下, 我们会限制甚至禁止使用某些特性. 这么做是为了保持代码清爽, 避免这些特性可能导致的各种问题. 指南中列举了这类特性, 并解释为什么这些特性被限制使用.

Google 主导的开源项目均符合本 指南 的规定.

zhangxuecheng 代码学习

[代码学习]C语言符号转换的一个问题

2009年4月2日

在CSDN社区闲逛, 看到有人问了这么一个问题:

打上注释1的那一行中,为什么要写“*(unsigned char *)src ”写成*src-*dst不好么?

源代码是这样的:

int __cdecl strcmp (const char * src,const char * dst)
{
int ret = 0 ;

/*1*/
while( ! (ret = *(unsigned char *)src – *(unsigned char *)dst) && *dst)
++src, ++dst;

if ( ret < 0 )
ret = -1 ;
else if ( ret > 0 )
ret = 1 ;

return( ret );
}

下面是高人的解答:

如果*src值为(char)1,*dst值为(char)255,那么
ret = *(unsigned char *)src – *(unsigned char *)dst的值为-254,而
ret = *src-*dst的值为2(char先转换为int,再做减法运算。转换时是符号扩展的),显然导致判断正负的错误

这个解答讲的很清楚了,但我还是觉得有点似懂非懂,于是乎查了下书<C:A Reference Manual 5th>,一共两个知识点:

  • 类型转换时的符号扩展, 这个很简单了, char型的有符号数如果转换成int型的有符号数, 高24bit会填充符号位, 即整数填0, 负数填1; 所以char转成int之后做得减法结果是不一样的;
  • 赋值时的类型转换, 赋值操作符左边和右边的表达式的类型应该是相同的, 如果不是, 就尝试把赋值操作符右边的表达式的值转换为左边的类型; 就是这里的char*转换成unsigned int*;

另外跑题一下, 翻书时看到的, C不允许把任何类型转换为数组或者函数类型;

  • 很多讲”指针与数组的区别”的文章中都会提到, 数组的类型是不能变的, 而指针的类型可以强制转换, 这是数组和指针的一个主要区别;
  • 但是这个区别的根本限制应该是上面这句话, 任何类型都不能转换成数组或者函数;比如书中提到的这个例子(6.2.8): extern int f(); double d; d=((double())f)(); 就是不允许的, 因为这行语句试图把f的返回值类型改为double; 如果没有extern声明, 就不确定了, 如果一个函数没有声明, 大部分编译器会假设其返回值为int.

zhangxuecheng 代码学习

函数与带参数宏的区别

2009年3月29日

刚看到jserv放出了一份c语言讲义:http://blog.linux.org.tw/~jserv/archives/002096.html

立即当下来拜读,其中有一个细节暂时没搞懂:

#include <stdio.h>

void f()

{ puts(”Function”); }

#define f() puts(”Macro”)

int main()

{

f();

f ();

(f)()

}

输出结果是:

Macro

Macro

Function

对C语言来说,函数和带参数的宏区别在什么地方呢?

这应该是个小问题,查一下C标准就能搞定,但是C标准参考那本书放在公司了,所以先记下来,周一查书看看。


首先感谢jserv大牛光临小博, 蓬荜生辉阿,呵呵.

今天才抽出时间想这个问题, 暂时的结论是这样:

  • 预处理器要求带参数宏的右侧的第一个token必须是左括号, 不过这只是一个猜测, 手头没有预处理器的详细标准;
  • 编译器来进行词法分析的时候, 首先应该是一个表达式求值, 求值的结果是指向f的函数指针, 然后接下来就是函数调用了.

不过第二条这个非常不确定, 刚对着C的语法范式推了十几遍, 始终不得要领, 每次都跑到死胡同里去了.

后面准备用C的lex和yacc定义做一个log程序, 将语法分析时的每个步骤都打印出来, 应该很容易.


后记:2009-07-28
这两天在看gcc的libcpp源代码,觉得这个问题其实很简单,预处理器是最早运行的,它会将符合带参数宏的形式全部展开,而编译器到了编译阶段才开始干活,so简单的一个道理….

zhangxuecheng 代码学习

[代码学习]利用宏扩展提高代码的可读性

2009年2月16日

上周看了一下ffmpeg,在ffmpeg.c->main()->avcodec_register_all()中看到这样一些代码:

REGISTER_DECODER (AASC, aasc);
REGISTER_DECODER (AMV, amv);
REGISTER_ENCDEC (ASV1, asv1);
REGISTER_ENCDEC (ASV2, asv2);
REGISTER_DECODER (AVS, avs);

很显然是在注册各种codec的结构体,一是把各codec相关的信息挂载到全局链表中,二要通过宏定义来判断哪些codec需要注册,哪些不需要.

REGISTER_DECODER的宏是这样定义的:

#define REGISTER_DECODER(X,x) {
extern AVCodec x##_decoder;
if(CONFIG_##X##_DECODER)  avcodec_register(&x##_decoder); }

对REGISTER_DECODER (AASC, aasc);来说,展开来就是这样:

extern AVCodec aasc_decoder;

if(CONFIG_AASC_DECODER)

avcodec_register(&aasc_decoder);

原理很简单,但是用的却很巧妙,主要是##这个宏用的人并不是很多,在这里倒是个很恰当的适用场景.

我觉得这种用法的好处有好几个:

  • 简化代码,使主要逻辑一目了然,这时最大的优点.我们现在的项目里就有很多这样的函数,宏掺杂在代码中,怎么排版都觉得很乱,一眼看上去根本不知道这些代码的目的是什么.
  • 减少coding工作量,这跟简化代码基本是一个意思,呵呵.

zhangxuecheng 代码学习

The New C Standard

2009年1月12日

刚在云风那儿看到的.

话说云风同学看书看到凌晨5点,汗,太疯狂了….

该书即将出版,amazon上已经有了,

顺便google了一下作者,Derek M. Jones,还有一篇文章.

电子版的下载地址在这里.


之前买了C语言参考手册,现在只看了一半多点,春节前可能没有时间再捡起来看了.

不过无论是C语言标准,动态语言实现,还是GCC的内部实现,都离不开编译原理这棵大树,掌握一个就可以旁通了,所以春节前的重点还是LCC,嗯.

zhangxuecheng 代码学习

[代码学习]什么是DRY原则(Don’t Repeat Yourself)?

2009年1月2日

http://www.newsmth.net/bbstcon.php?board=Programming&gid=109898

year = ORIGINYEAR; /* = 1980 */
while (days > 365)
{
if (IsLeapYear(year))
{
if (days > 366)
{
days -= 366;
year += 1;
}
}
else
{
days -= 365;
year += 1;
}
}

按照DRY原则做修改:

year = ORIGINYEAR; /* = 1980 */
for (;;)
{
int year_days = 365 + IsLeapYear(year);
if (days > year_days)
{
days -= year_days;
++year;
}
else
{
break;
}
}

google了一下DRY,找到了一些链接,似乎与code reuse有关?

zhangxuecheng 代码学习