Chinaunix首页 | 论坛 | 博客
  • 博客访问: 143506
  • 博文数量: 32
  • 博客积分: 2050
  • 博客等级: 大尉
  • 技术积分: 335
  • 用 户 组: 普通用户
  • 注册时间: 2006-08-10 09:43
文章分类

全部博文(32)

文章存档

2012年(1)

2011年(1)

2009年(1)

2008年(5)

2007年(22)

2006年(2)

我的朋友

分类: C/C++

2007-09-10 16:45:13

这其实不是原创, 只是从sorceforge上选的一些c的faq. 但是转载也要注明出处啊, 厚道点做人.....
 
 
 
 
2.3 怎样定义和声明全局变量和函数最好?
 
 
首先, 尽管一个全局变量或函数可以 (在多个编译单元中) 有多处 ``声明'', 但是 ``定义'' 却只能允许出现一次。定义是分配空间并赋初值 (如果有) 的声明。最好的安排是在某个相关的 .c 文件中定义, 然后在头文件  (.h) 中进行外部声明, 在需要使用的时候, 只要包含对应的头文件即可。定义变量的 .c 文件也应该包含该头文件, 以便编译器检查定义和声明的一致性。

这条规则提供了高度的可移植性: 它和 ANSI C 标准一致, 同时也兼容大多数  ANSI 前的编译器和连接器。Unix 编译器和连接器通常使用 ``通用模式'' 允许多重定义, 只要保证最多对一处进行初始化就可以了; ANSI C 标准称这种行为为 ``公共扩展'', 没有语带双关的意思。

可以使用预处理技巧来使类似

	DEFINE(int, i);
的语句在一个头文件中只出现一次, 然后根据某个宏的设定在需要的时候转化成定义或声明。但不清楚这样的麻烦是否值得。

如果希望让编译器检查声明的一致性, 一定要把全局声明放到头文件中。特别是, 永远不要把外部函数的原型放到 .c 文件中: 通常它与定义的一致性不能得到检查, 而矛盾的原型比不用还糟糕。

 

2.6 我似乎不能成功定义一个链表。我试过  typedef struct { char *item; NODEPTR next; } *NODEPTR; 但是编译器报了错误信息。难道在C语言中一个结构不能包含指向自己的指针吗?

C 语言中的结构当然可以包含指向自己的指针; [, 第 6.5 节] 的讨论和例子表明了这点。 NODEPTR 例子的问题是在声明 next 域的时候  typedef 还没有定义。为了解决这个问题, 首先赋予这个结构一个标签  (``struct node'')。然后, 声明 ``next'' 域为  ``struct node *'', 或者分开 typedef 定义和结构定义, 或者两者都采纳。以下是一个修改后的版本:

	struct node {
	    char *item;
	    struct node *next;
	};

	typedef struct node *NODEPTR;

至少还有三种同样正确的方法解决这个问题。

在用 typedef 定义互相引用的两个结构时也会产生类似的问题, 可以用同样的方法解决。

 

2.7 怎样建立和理解非常复杂的声明?

例如定义一个包含 N 个指向返回指向字符的指针的函数的指针的数组? 这个问题至少有以下 3 种答案:

  1. char *(*(*a[N])())();
  2. 用 typedef 逐步完成声明:
        typedef char *pc;       /* 字符指针           */
        typedef pc fpc();       /* 返回字符指针的函数 */
        typedef fpc *pfpc;      /* 上面函数的指针     */
        typedef pfpc fpfpc();   /* 返回函数指针的函数 */
        typedef fpfpc *pfpfpc;  /* 上面函数的指针     */
        pfpfpc a[N];	    /* 上面指针的数组     */
    

  3. 使用 cdecl 程序, 它可以把英文翻译成 C 或者把 C 翻译成英文:
        cdecl> declare a as array of pointer to function returning
           pointer to function returning pointer to char
        char *(*(*a[])())()
    
    通过类型转换, cdecl 也可以用于解释复杂的声明, 指出参数应该进入哪一对括号  (如同在上述的复杂函数定义中).

一本好的 C 语言书都会解释如何 ``从内到外'' 解释和理解这样复杂的 C 语言声明 (``模拟声明使用'')。

上文的例子中的函数指针声明还没有包括参数类型信息。如果参数有复杂类型, 声明就会变得真正的混乱了。现代的 cdecl 版本可以提供帮助。

 

2.8 函数只定义了一次, 调用了一次, 但编译器提示非法重定义了。

 

在范围内没有声明就调用 (可能是第一次调用在函数的定义之前) 的函数被认为返回整型 (int) (且没有任何参数类型信息), 如果函数在后边声明或定义成其它类型就会导致矛盾。所有函数 (非整型函数一定要) 必须在调用之前声明。

另一个可能的原因是该函数与某个头文件中声明的另一个函数同名。

 

2.10 对于没有初始化的变量的初始值可以作怎样的假定?

如果一个全局变量初始值为 ``零", 它可否作为空指针或浮点零? 具有 ``静态'' 生存期的未初始化变量  (即, 在函数外声明的变量和有静态存储类型的变量) 可以确保初始值为零, 就像程序员键入了  ``=0'' 一样。因此, 这些变量如果是指针会被初始化为正确的空指针, 如果是浮点数会被初始化为 0.0 (或正确的类型, 参见第  章)。

具有 ``自动'' 生存期的变量 (即, 没有静态存储类型的局部变量) 如果没有显示地初始化, 则包含的是垃圾内容。对垃圾内容不能作任何有用的假设。

这些规则也适用于数组和结构 (称为 ``聚合体'' ); 对于初始化来说, 数组和结构都被认为是 ``变量''。

用 malloc() 和 realloc() 动态分配的内存也可能包含垃圾数据, 因此必须由调用者正确地初始化。用 calloc()  获得的内存为全零, 但这对指针和浮点值不一定有用.

 

3.6 我遇到这样声明结构的代码: struct name { int namelen; char namestr[1];}; 然后又使用一些内存分配技巧使 namestr  数组用起来好像有多个元素。这样合法和可移植吗?

这种技术十分普遍, 尽管 Dennis Ritchie 称之为 ``和C 实现的无保证的亲密接触"。官方的解释认定它没有严格遵守 C 标准, 尽管它看来在所有的实现中都可以工作。仔细检查数组边界的编译器可能会发出警告。

另一种可能是把变长的元素声明为很大, 而不是很小; 在上例中:

    ...
    char namestr[MAXSIZE];

MAXSIZE 比任何可能存储的 name 值都大。但是, 这种技术似乎也不完全符合标准的严格解释。这些 ``亲密'' 结构都必须小心使用, 因为只有程序员知道它的大小, 而编译器却一无所知。

C99 引入了 ``灵活数组域'' 概念, 允许结构的最后一个域省略数组大小。这为类似问题提供了一个圆满的解决方案。

 

3.9 怎样从/向数据文件读/写结构?

用 fwrite() 写一个结构相对简单:

    fwrite(&somestruct, sizeof somestruct, 1, fp);

对应的 fread() 调用可以再把它读回来。但是这样写出的文件却不能移植 (参见问题  和 )。同时注意如果结构包含任何指针, 则只有指针值会被写入文件, 当它们再次读回来的时候, 很可能已经失效。最后, 为了广泛的移植, 你必须用 ``b'' 标志打开文件; 参见问题 。

移植性更好的方案是写一对函数, 用可移植  (可能甚至是人可读) 的方式按域读写结构, 尽管开始可能工作量稍大。

 

3.11 为什么 sizeof 返回的值大于结构的期望值, 是不是尾部有填充?

为了确保分配连续的结构数组时正确对齐, 结构可能有这种尾部填充。即使结构不是数组的成员, 填充也会保持, 以便 sizeof 能够总是返回一致的大小。参见问题 。

 

3.12 如何确定域在结构中的字节偏移?

ANSI C 在  中定义了  offsetof() 宏, 用 offsetof(struct s, f) 可以计算出域 f 在结构 s 中的偏移量。 如果出于某种原因, 你需要自己实现这个功能, 可以使用下边这样的代码:

	#define offsetof(type, f) ((size_t) \
	    ((char *)&((type *)0)->f - (char *)(type *)0))

这种实现不是 100% 的可移植; 某些编译器可能会合法地拒绝接受。

 

4.2 使用我的编译器,下面的代码  int i=7; printf("%d\n", i++ * i++); 返回 49?不管按什么顺序计算, 难道不该打印出56吗?

尽管后缀自加和后缀自减操作符 ++ 和 -- 在输出其旧值之后才会执行运算, 但这里的``之后"常常被误解。没有任何保证确保自增或自减会在输出变量原值之后和对表达式的其它部分进行计算之前立即进行。也不能保证变量的更新会在表达式 ``完成" (按照 ANSI C 的术语, 在下一个 ``序列点" 之前, 参见问题 ) 之前的某个时刻进行。本例中, 编译器选择使用变量的旧值相乘以后再对二者进行自增运算。

包含多个不确定的副作用的代码的行为总是被认为未定义。(简单而言, ``多个不确定副作用" 是指在同一个表达式中使用导致同一对象修改两次或修改以后又被引用的自增, 自减和赋值操作符的任何组合。这是一个粗略的定义; 严格的定义参见问题 , ``未定义" 的含义参见问题 。) 甚至都不要试图探究这些东西在你的编译器中是如何实现的 (这与许多 C 教科书上的弱智练习正好相反); 正如  K&R 明智地指出, ``如果你不知道它们在不同的机器上如何实现, 这样的无知可能恰恰会有助于保护你。

阅读(1612) | 评论(0) | 转发(0) |
0

上一篇:grub启动过程

下一篇:c语言常见问题集(2)

给主人留下些什么吧!~~