ulib 闲谈

2011年10月22日 16:36

 

开始这个系列博客之前 先来说点闲话。(我希望我能写完把。之前的那本书说翻译完进度也超慢。关键是觉得Linux桌面横竖都是死。)
 
为什么开始这个东西。要从我大学时代说起。
虽然从我们那级开始就没有大神了,但是我上一级的师兄们,还是很给力的出了两个大神。
很幸运,我都认识。并且一直指导我方方面面,感谢他们。
可惜天资愚钝。好吃懒做。终究连大神的边都没有摸到。心中却一直还有想努力学习的冲动。
一直缠着两位大神,想套取点《辟邪剑谱》《葵花宝典》之类的武功秘籍。
奈何最近才慢慢顿悟,大神们的武功都是返璞归真、大道至简、重剑无锋。每天都轧马步,打沙袋练就的。
 
这样的话,就自己静心下来好好学习呗。
谈大神,一直在赤裸裸的炫耀他自己的函数库已经超越FreeBSD库的效率,如何如何犀利。奈何,又发挥好吃懒做的精神。一直未成努力学习。
正直花好月圆之夜,谈大神放下屠刀。把这个神器般的函数库在google code上开源了。大好机会,不能再放过。这个主角终于登场——ulib。
这个系列博文就是记录怎么一点一点学习ulib
 
这是地址。大家需要代码的这里猛击用svn下载。
http://code.google.com/p/ulib/
 
至于这个库有多生猛,猛击下面查看谈大神的博文,他自己用洋文炫耀过。
http://blog.sina.com.cn/s/blog_5e9472940100umhm.html
 
Aligned Hashing - Probabily the fastest hashing scheme to date 啧啧看看标题就炫耀的不行。
 
至于真假,大家不是已经有地址下载了么。自行尝试对比把玩。方正我是在入门阶段。他又是大神。我是反驳不了了。
 
so 事已至此。我准备的是写两个部分,一个是学习ulib产生的一些相关内容,比如此文一个分类。
一个是直接运用ulib的一个分类。
 
 
 
2011-10-22  某90高龄基督教信徒预言世界末日留

This is a short document describing the preferred coding style for the

linux kernel.  Coding style is very personal, and I won't _force_ my
views on anybody, but this is what goes for anything that I have to be
able to maintain, and I'd prefer it for most other things too.  Please
at least consider the points made here.
 
First off, I'd suggest printing out a copy of the GNU coding standards,
and NOT read it.  Burn them, it's a great symbolic gesture.
 
这是一份简短的,描述linux内核首选编码风格的文档。编码风格是很个人
化的东西,而且我也不愿意把我的观点强加给任何人,不过这里所讲述的
是我必须要维护的代码所遵守的风格,并且我也希望绝大多数其他代码也
能遵守这个风格。所以请至少考虑一下本文所述的观点。

首先,我建议你打印一份GNU的编码规范,然后不要读它。烧掉它,这是
一个很高调的具有象征意义的姿态。
 
Anyway, here goes:
 
 
Chapter 1: Indentation
 
Tabs are 8 characters, and thus indentations are also 8 characters.
There are heretic movements that try to make indentations 4 (or even 2!)
characters deep, and that is akin to trying to define the value of PI to
be 3.
制表符是8个字符,所以缩进也是8个字符。有些异端运动试图将缩进变
为4(乃至2)个字符深,这跟尝试着将圆周率PI的值定义为3没什么两样。
理由:缩进的全部意义就在于清楚的定义一个控制块起止于何处。尤其
是当你盯着你的屏幕连续看了20小时之后,你将会发现大一点的缩进将
会使你更容易分辨缩进
 
Rationale: The whole idea behind indentation is to clearly define where
a block of control starts and ends.  Especially when you've been looking
at your screen for 20 straight hours, you'll find it a lot easier to see
how the indentation works if you have large indentations.
 
Now, some people will claim that having 8-character indentations makes
the code move too far to the right, and makes it hard to read on a
80-character terminal screen.  The answer to that is that if you need
more than 3 levels of indentation, you're screwed anyway, and should fix
your program.
现在,有些人会抱怨8个字符的缩进会使代码向右边移动的太远,在80个
字符的终端屏幕上就很难读这样的代码。这个问题的答案是,如果你需要
3级以上的缩进,不管缩进深度如何你的代码已经有问题了,应该修正你
的程序。
 
In short, 8-char indents make things easier to read, and have the added
benefit of warning you when you're nesting your functions too deep.
Heed that warning.
简而言之,8个字符的缩进可以让代码更容易阅读,还有一个好处是当你的
函数嵌套太深的时候可以向你提出告警。请留意这个警告。
 
The preferred way to ease multiple indentation levels in a switch statement is
to align the "switch" and its subordinate "case" labels in the same column
instead of "double-indenting" the "case" labels.  E.g.:
在switch语句中消除多级缩进的首选的方式是让“switch”和从属于它的“case”标签对
齐于同一列,而不要“两次缩进”“case”标签。比如:
switch (suffix) {
case 'G':
case 'g':
	mem <<= 30;
	break;
case 'M':
case 'm':
	mem <<= 20;
	break;
case 'K':
case 'k':
	mem <<= 10;
	/* fall through */
default:
	break;
}
 
 
Don't put multiple statements on a single line unless you have
something to hide:
不要把多个语句放在一行里,除非你有什么东西要隐藏:
if (condition) do_this;
  do_something_everytime
 
Don't put multiple assignments on a single line either.  Kernel coding style
is super simple.  Avoid tricky expressions.
也不要在一行里放多个赋值语句。内核编码风格超级简单。就是请避免使用怪
异的表达式。
 
Outside of comments, documentation and except in Kconfig, spaces are never
used for indentation, and the above example is deliberately broken.
除了注释、文档和Kconfig之外,不要使用空格来缩进,前面的例子是例外,是有
意为之。
 
Get a decent editor and don't leave whitespace at the end of lines.
选用一个好的编辑器,不要在行尾留空格
 
 
Chapter 2: Breaking long lines and strings
第二章:把长的行和字符串打散
 
Coding style is all about readability and maintainability using commonly
available tools.
编码风格的意义就在于使用平常使用的工具来维持代码的可读性和可维护性。
 
The limit on the length of lines is 80 columns and this is a strongly
preferred limit.
每一行的长度的限制是80列,我们强烈建议您遵守这个惯例。
 
Statements longer than 80 columns will be broken into sensible chunks.
Descendants are always substantially shorter than the parent and are placed
substantially to the right. The same applies to function headers with a long
argument list. Long strings are as well broken into shorter strings. The
only exception to this is where exceeding 80 columns significantly increases
readability and does not hide information.
长于80列的语句要打散成有意义的片段。每个片段要明显短于原来的语句,而
且放置的位置也明显的靠右。同样的规则也适用于有很长参数列表的函数头。
长字符串也要打散成较短的字符串。唯一的例外是超过80列可以大幅度提高可
读性并且不会隐藏信息的情况
void fun(int a, int b, int c)
{
	if (condition)
		printk(KERN_WARNING "Warning this is a long printk with "
						"3 parameters a: %u b: %u "
						"c: %u \n", a, b, c);
	else
		next_statement;
}
 
 
Chapter 3: Placing Braces and Spaces
第三章:大括号和空格的放置
 
The other issue that always comes up in C styling is the placement of
braces.  Unlike the indent size, there are few technical reasons to
choose one placement strategy over the other, but the preferred way, as
shown to us by the prophets Kernighan and Ritchie, is to put the opening
brace last on the line, and put the closing brace first, thusly:
C语言风格中另外一个常见问题是大括号的放置。和缩进大小不同,选
择或弃用某种放置策略并没有多少技术上的原因,不过首选的方式,就
像Kernighan和Ritchie展示给我们的,是把起始大括号放在行尾,而把
结束大括号放在行首,所以:
if (x is true) {
	we do y
}
 
This applies to all non-function statement blocks (if, switch, for,
while, do).  E.g.:
这适用于所有的非函数语句块(if、switch、for、while、do)。比如:
switch (action) {
case KOBJ_ADD:
	return "add";
case KOBJ_REMOVE:
	return "remove";
case KOBJ_CHANGE:
	return "change";
default:
	return NULL;
}
 
However, there is one special case, namely functions: they have the
opening brace at the beginning of the next line, thus:
不过,有一种特殊情况,命名函数:它们的起始大括号放置于下一行的
开头,这样:
int function(int x)
{
	body of function
}
 
Heretic people all over the world have claimed that this inconsistency
is ...  well ...  inconsistent, but all right-thinking people know that
(a) K&R are _right_ and (b) K&R are right.  Besides, functions are
special anyway (you can't nest them in C).
全世界的异端可能会抱怨这个不一致性,呃…确实是不一致的,不过所
有思维健全的人都知道(a)K&R是 正确的, 并且(b)K&R是正确的。
另外,不管怎样函数都是特殊的(在C语言中,函数是不能嵌套的)。
 
Note that the closing brace is empty on a line of its own, _except_ in
the cases where it is followed by a continuation of the same statement,
ie a "while" in a do-statement or an "else" in an if-statement, like
this:
注意结束大括号独自占据一行,除非它后面跟着同一个语句的剩余部分,
比如说do语句中的“while”或者if语句中的“else”,像这样:
do {
	body of do-loop
} while (condition);
 
and
 
if (x == y) {
	..
} else if (x > y) {
	...
} else {
	....
}
 
Rationale: K&R.
 
Also, note that this brace-placement also minimizes the number of empty
(or almost empty) lines, without any loss of readability.  Thus, as the
supply of new-lines on your screen is not a renewable resource (think
25-line terminal screens here), you have more empty lines to put
comments on.
理由:K&R。
也请注意这种大括号的放置方式还能使空(或者差不多空的)行的数量最
小化,同时不失可读性。因此,由于你的屏幕上的新行的供应不是可回收
的资源(想想25行的终端屏幕),你将会有更多的空行来放置注释。
 
Do not unnecessarily use braces where a single statement will do.
仅有一个单独的语句时,不用加不必要的大括号。
if (condition)
	action();
and
if (condition)
	do_this();
else
	do_that();
 
This does not apply if one branch of a conditional statement is a single
statement. Use braces in both branches.
这点不适用于本身为某个条件语句的一个分支的单独语句。这时应该两个
分支里都使用大括号。
if (condition) {
	do_this();
	do_that();
} else {
	otherwise();
}
 
 
3.1:  Spaces
3.1:空格
 
Linux kernel style for use of spaces depends (mostly) on
function-versus-keyword usage.  Use a space after (most) keywords.  The
notable exceptions are sizeof, typeof, alignof, and __attribute__, which look
somewhat like functions (and are usually used with parentheses in Linux,
although they are not required in the language, as in: "sizeof info" after
"struct fileinfo info;" is declared).
Linux内核的空格使用方格(主要)取决于它是用于函数还是关键字。
(大多数)关键字后要加一个空格。值得注意的例外是
sizeof、typeof、alignof和__attribute__,
这些关键字在一定程度上看起来更像函数(它们在Linux里也常常伴
随小括号使用,尽管在C语言里这样的小括号不是必需的,
就像“struct fileinfo info”声明过后的“sizeof info”)
 
So use a space after these keywords:
if, switch, case, for, do, while
but not with sizeof, typeof, alignof, or __attribute__.  E.g.,
所以在这些关键字之后放一个空格:
 if, switch, case, for, do, while
但是不在sizeof、typeof、alignof或者__attribute__这些关键字之后放空格。例如,
s = sizeof(struct file);
 
Do not add spaces around (inside) parenthesized expressions.  This example is
*bad*:
不要在小括号里的表达式两侧加空格。这是一个反例:
s = sizeof( struct file );
 
 
When declaring pointer data or a function that returns a pointer type, the
preferred use of '*' is adjacent to the data name or function name and not
adjacent to the type name.  Examples:
当声明指针类型或者返回指针类型的函数时,“*”的首选使用方式是使之靠近
变量名或者函数名,而不是靠近类型名。例子:
char *linux_banner;
unsigned long long memparse(char *ptr, char **retptr);
char *match_strdup(substring_t *s);
 
Use one space around (on each side of) most binary and ternary operators,
such as any of these:
在大多数二元和三元操作符两侧使用一个空格,例如下面所有这些操作符:
 
=  +  -  <  >  *  /  %  |  &  ^  <=  >=  ==  !=  ?  :
 
but no space after unary operators:
但是一元操作符后不要加空格:
&  *  +  -  ~  !  sizeof  typeof  alignof  __attribute__  defined
 
no space before the postfix increment & decrement unary operators:
后缀自增和自减一元操作符前不加空格:
++  --
 
no space after the prefix increment & decrement unary operators:
前缀自增和自减一元操作符后不加空格:
++  --
 
and no space around the '.' and "->" structure member operators.
“.”和“->”结构体成员操作符前后不加空格。
 
Do not leave trailing whitespace at the ends of lines.  Some editors with
"smart" indentation will insert whitespace at the beginning of new lines as
appropriate, so you can start typing the next line of code right away.
However, some such editors do not remove the whitespace if you end up not
putting a line of code there, such as if you leave a blank line.  As a result,
you end up with lines containing trailing whitespace.
不要在行尾留空白。有些可以自动缩进的编辑器会在新行的行首加入适量的空白,
然后你就可以直接在那一行输入代码。不过假如你最后没有在那一行输入代码,
有些编辑器就不会移除已经加入的空白,就像你故意留下一个只有空白的行。包
含行尾空白的行就这样产生了。
 
Git will warn you about patches that introduce trailing whitespace, and can
optionally strip the trailing whitespace for you; however, if applying a series
of patches, this may make later patches in the series fail by changing their
context lines.
当Git发现补丁包含了行尾空白的时候会警告你,并且可以应你的要求去掉行尾
空白;不过如果你是正在打一系列补丁,这样做会导致后面的补丁失败,因为
你改变了补丁的上下文。
 
 
Chapter 4: Naming
 第四章:命名

如果你怕混淆了你的本地变量名,你就遇到另一个问题了,叫做函数增长荷尔蒙失衡综合症。请看第六章(函数)。
C is a Spartan language, and so should your naming be.  Unlike Modula-2
and Pascal programmers, C programmers do not use cute names like
ThisVariableIsATemporaryCounter.  A C programmer would call that
variable "tmp", which is much easier to write, and not the least more
difficult to understand.
C 是一个简朴的语言,你的命名也应该这样。和Modula-2和Pascal程序员不同,
C程序员不使用类似ThisVariableIsATemporaryCounter这样华丽的名字。C程序
员会称那个变量为“tmp”,这样写起来会更容易,而且至少不会令其难于理解。
 
HOWEVER, while mixed-case names are frowned upon, descriptive names for
global variables are a must.  To call a global function "foo" is a
shooting offense.
不过,虽然混用大小写的名字是不提倡使用的,但是全局变量还是需要一个具描
述性的名字。称一个全局函数为“foo”是一个难以饶恕的错误。
 
GLOBAL variables (to be used only if you _really_ need them) need to
have descriptive names, as do global functions.  If you have a function
that counts the number of active users, you should call that
"count_active_users()" or similar, you should _not_ call it "cntusr()".
全局变量(只有当你真正需要它们的时候再用它)需要有一个具描述性的
名字,就像全局函数。如果你有一个可以计算活动用户数量的函数,你应
该叫它“count_active_users()”或者类似的名字,你不应该叫它“cntuser()”。
 
Encoding the type of a function into the name (so-called Hungarian
notation) is brain damaged - the compiler knows the types anyway and can
check those, and it only confuses the programmer.  No wonder MicroSoft
makes buggy programs.
在函数名中包含函数类型(所谓的匈牙利命名法)是脑子出了问题——编译
知道那些类型而且能够检查那些类型,这样做只能把程序员弄糊涂了。难怪微
软总是制造出有问题的程序。
 
LOCAL variable names should be short, and to the point.  If you have
some random integer loop counter, it should probably be called "i".
Calling it "loop_counter" is non-productive, if there is no chance of it
being mis-understood.  Similarly, "tmp" can be just about any type of
variable that is used to hold a temporary value.
 
If you are afraid to mix up your local variable names, you have another
problem, which is called the function-growth-hormone-imbalance syndrome.
See chapter 6 (Functions).
本地变量名应该简短,而且能够表达相关的含义。如果你有一些随机的整数型的
循环计数器,它应该被称为“i”。叫它“loop_counter”并无益处,如果它没有可能被
误解的话。类似的“tmp”可以用来称呼任意类型的临时变量。
 
 
Chapter 5: Typedefs
第五章:Typedef
Please don't use things like "vps_t".
不要使用类似“vps_t”之类的东西。
 
It's a _mistake_ to use typedef for structures and pointers. When you see a
对结构体和指针使用typedef是一个错误。当你在代码里看到:
vps_t a;
 
 
in the source, what does it mean?
这代表什么意思呢?
 
In contrast, if it says
相反,如果是这样
struct virtual_container *a;
 
 
you can actually tell what "a" is.
你就知道“a”是什么了。
 
Lots of people think that typedefs "help readability". Not so. They are
useful only for:
很多人认为typedef“能提高可读性”。实际不是这样的。它们只在下列情况下有用:
 
 (a) totally opaque objects (where the typedef is actively used to _hide_
     what the object is).
(a) 完全不透明的对象(这种情况下要主动使用typedef来隐藏这个对象实际
上是什么)
 
     Example: "pte_t" etc. opaque objects that you can only access using
     the proper accessor functions.
     例如:“pte_t”等不透明对象,你只能用合适的访问函数来访问它们。
 
     NOTE! Opaqueness and "accessor functions" are not good in themselves.
     The reason we have them for things like pte_t etc. is that there
     really is absolutely _zero_ portably accessible information there.
     注意!不透明性和“访问函数本身”是不好的。我们使用pte_t等类型的原因在于真的
     是完全没有任何共用的可访问信息。
 
 (b) Clear integer types, where the abstraction _helps_ avoid confusion
     whether it is "int" or "long".
(b) 清楚的整数类型,这样抽象层就可以帮助我们消除到底是"int"还是"long"的混淆。
 
     u8/u16/u32 are perfectly fine typedefs, although they fit into
     category (d) better than here.
    u8/u16/u32是完全没有问题的typedef,不过它们更符合(d)中所
    言,而不是这里。再次注意!要这样做,必须事出有因。如果某
    个变量是“unsigned long“,那么没有必要
 
     NOTE! Again - there needs to be a _reason_ for this. If something is
     "unsigned long", then there's no reason to do
 
typedef unsigned long myflags_t;
 
     but if there is a clear reason for why it under certain circumstances
     might be an "unsigned int" and under other configurations might be
     "unsigned long", then by all means go ahead and use a typedef.
   不过如果有一个明确的原因,比如它在某种情况下可能会是一个“unsigned int”而
    在其他情况下可能为“unsigned long”,那么就不要犹豫,请务必使用typedef。
 
 (c) when you use sparse to literally create a _new_ type for
     type-checking.
(c) 当你使用sparse按字面的创建一个新类型来做类型检查的时候。
 
 (d) New types which are identical to standard C99 types, in certain
     exceptional circumstances.
(d) 和标准C99类型相同的类型,在某些例外的情况下。
 
     Although it would only take a short amount of time for the eyes and
     brain to become accustomed to the standard types like 'uint32_t',
     some people object to their use anyway.
    虽然让眼睛和脑筋来适应新的标准类型比如“uint32_t”不需要花很多时间,可以有
    些人仍然拒绝使用它们。
 
     Therefore, the Linux-specific 'u8/u16/u32/u64' types and their
     signed equivalents which are identical to standard types are
     permitted -- although they are not mandatory in new code of your
     own.
    因此,Linux特有的等同于标准类型的“u8/u16/u32/u64”类型和它们
    的有符号类型是被允许的——尽管在你自己的新代码中,它们不是强
    制要求要使用的。
 
     When editing existing code which already uses one or the other set
     of types, you should conform to the existing choices in that code.
    当编辑已经使用了某个类型集的已有代码时,你应该遵循那些代码中已经做出的选择。
 
 (e) Types safe for use in userspace.
(e) 可以在用户空间安全使用的类型。
     In certain structures which are visible to userspace, we cannot
     require C99 types and cannot use the 'u32' form above. Thus, we
     use __u32 and similar types in all structures which are shared
     with userspace.
在某些用户空间可见的结构体里,我们不能要求C99类型而且不能用上面
提到的“u32”类型。
 
Maybe there are other cases too, but the rule should basically be to NEVER
EVER use a typedef unless you can clearly match one of those rules.
    因此,我们在与用户空间共享的所有结构体中使用__u32和类似的类型。
可能还有其他的情况,不过基本的规则是永远不要使用typedef,除非你可
以明确的应用上述某个规则中的一个。
 
In general, a pointer, or a struct that has elements that can reasonably
be directly accessed should _never_ be a typedef.
总的来说,如果一个指针或者一个结构体里的元素可以合理的被直接访问到,那么它们就不应该是一个typedef
 
 
Chapter 6: Functions
第六章:函数
 
Functions should be short and sweet, and do just one thing.  They should
fit on one or two screenfuls of text (the ISO/ANSI screen size is 80x24,
as we all know), and do one thing and do that well.
函数应该简短而漂亮,并且只完成一件事情。函数应该可以一屏或者两屏显
示完(我们都知道ISO/ANSI屏幕大小是80x24),只做一件事情,而且把它做好。
 
The maximum length of a function is inversely proportional to the
complexity and indentation level of that function.  So, if you have a
conceptually simple function that is just one long (but simple)
case-statement, where you have to do lots of small things for a lot of
different cases, it's OK to have a longer function.
一个函数的最大长度是和该函数的复杂度和缩进级数成反比的。所以,
如果你有一个理论上很简单的只有一个很长(但是简单)的case语句的
函数,而且你需要在每个case里做很多很小的事情,这样的函数尽管很
长,但也是可以的。
 
However, if you have a complex function, and you suspect that a
less-than-gifted first-year high-school student might not even
understand what the function is all about, you should adhere to the
maximum limits all the more closely.  Use helper functions with
descriptive names (you can ask the compiler to in-line them if you think
it's performance-critical, and it will probably do a better job of it
than you would have done).
不过,如果你有一个复杂的函数,而且你怀疑一个天分不是很高的高中一
年级学生可能甚至搞不清楚这个函数的目的,你应该更严格的遵守最大限
制。使用辅助函数,并为之取个具描述性的名字(如果你觉得其对性能要
求严格的话,你可以要求编译器将它们内联展开,它往往会比你更好的完
成任务。)
 
Another measure of the function is the number of local variables.  They
shouldn't exceed 5-10, or you're doing something wrong.  Re-think the
function, and split it into smaller pieces.  A human brain can
generally easily keep track of about 7 different things, anything more
and it gets confused.  You know you're brilliant, but maybe you'd like
to understand what you did 2 weeks from now.
函数的另外一个衡量标准是本地变量的数量。此数量不应超过5-10个,
否则你的函数就有问题了。重新考虑一下你的函数,把它分拆成更小的函
数。人的大脑一般可以轻松的同时跟踪7个不同的事物,如果再增多的话,
就会糊涂了。即便你聪颖过人,你也可能会记不清你2个星期前做过的事情。
 
In source files, separate functions with one blank line.  If the function is
exported, the EXPORT* macro for it should follow immediately after the closing
function brace line.  E.g.:
在源文件里,使用空行隔开不同的函数。如果该函数需要被导出,它的
EXPORT*宏应该紧贴在它的结束大括号之下。比如:
int system_is_up(void)
{
	return system_state == SYSTEM_RUNNING;
}
EXPORT_SYMBOL(system_is_up);
 
In function prototypes, include parameter names with their data types.
Although this is not required by the C language, it is preferred in Linux
because it is a simple way to add valuable information for the reader.
在函数原型中,包含函数名和它们的数据类型。虽然C语言里没有这样的
要求,在Linux里这是提倡的做法,因为这样可以很简单的给读者提供更
多的有价值的信息。
 
 
Chapter 7: Centralized exiting of functions
第七章:集中的函数退出途径
 
Albeit deprecated by some people, the equivalent of the goto statement is
used frequently by compilers in form of the unconditional jump instruction.
 
The goto statement comes in handy when a function exits from multiple
locations and some common work such as cleanup has to be done.
虽然被某些人声称已经过时,但是goto语句的等价物还是经常被编译器所
使用,具体形式是无条件跳转指令。当一个函数从多个位置退出并且需要
做一些通用的清洁工作的时候,goto的好处就显现出来了
 
The rationale is:
理由是:
- unconditional statements are easier to understand and follow
- nesting is reduced
- errors by not updating individual exit points when making
    modifications are prevented
- saves the compiler work to optimize redundant code away ;)

- 无条件语句容易理解和跟踪
- 嵌套程度减小
- 可以避免由于修改时忘记更新某个单独的退出点而导致的错误
- 减轻了编译器的工作,无需删除冗余代码;)
int fun(int a)
{
	int result = 0;
	char *buffer = kmalloc(SIZE);

	if (buffer == NULL)
		return -ENOMEM;

	if (condition1) {
		while (loop1) {
			...
		}
		result = 1;
		goto out;
	}
	...
out:
	kfree(buffer);
	return result;
}
 
 
Chapter 8: Commenting
第八章:注释
 
Comments are good, but there is also a danger of over-commenting.  NEVER
try to explain HOW your code works in a comment: it's much better to
write the code so that the _working_ is obvious, and it's a waste of
time to explain badly written code.
注释是好的,不过有过度注释的危险。永远不要在注释里解释你的代码是如何
运作的:更好的做法是让别人一看你的代码就可以明白,解释写的很差的代码
是浪费时间。
 
 
Generally, you want your comments to tell WHAT your code does, not HOW.
Also, try to avoid putting comments inside a function body: if the
function is so complex that you need to separately comment parts of it,
you should probably go back to chapter 6 for a while.  You can make
small comments to note or warn about something particularly clever (or
ugly), but try to avoid excess.  Instead, put the comments at the head
of the function, telling people what it does, and possibly WHY it does
it.
一般的,你想要你的注释告诉别人你的代码做了什么,而不是怎么做的。
也请你不要把注释放在一个函数体内部:如果函数复杂到你需要独立的注释其
中的一部分,你很可能需要回到第六章看一看。你可以做一些小注释来注明或
警告某些很聪明(或者槽糕)的做法,但不要加太多。你应该做的,是把注释
放在函数的头部,告诉人们它做了什么,也可以加上它做这些事情的原因。
 
When commenting the kernel API functions, please use the kernel-doc format.
See the files Documentation/kernel-doc-nano-HOWTO.txt and scripts/kernel-doc
for details.
当注释内核API函数时,请使用kernel-doc格式。请看
Documentation/kernel-doc-nano-HOWTO.txt和scripts/kernel-doc以获得详细信息。
 
Linux style for comments is the C89 "/* ... */" style.
Don't use C99-style "// ..." comments.
Linux的注释风格是C89“/* ... */”风格。不要使用C99风格“// ...”注释。
 
The preferred style for long (multi-line) comments is:
长(多行)的首选注释风格是:
/*
* This is the preferred style for multi-line
* comments in the Linux kernel source code.
* Please use it consistently.
*
* Description:  A column of asterisks on the left side,
* with beginning and ending almost-blank lines.
*/
 
 
It's also important to comment data, whether they are basic types or derived
types.  To this end, use just one data declaration per line (no commas for
multiple data declarations).  This leaves you room for a small comment on each
item, explaining its use.
注释数据也是很重要的,不管是基本类型还是衍生类型。为了方便实现这一点,
每一行应只声明一个数据(不要使用逗号来一次声明多个数据)。这样你就有空
间来为每个数据写一段小注释来解释它们的用途了
 
 
Chapter 9: You've made a mess of it
第九章:你已经把事情弄糟了
 
That's OK, we all do.  You've probably been told by your long-time Unix
user helper that "GNU emacs" automatically formats the C sources for
you, and you've noticed that yes, it does do that, but the defaults it
uses are less than desirable (in fact, they are worse than random
typing - an infinite number of monkeys typing into GNU emacs would never
make a good program).
这没什么,我们都是这样。可能你的使用了很长时间Unix的朋友已经告诉你
“GNU emacs”能自动帮你格式化C源代码,而且你也注意到了,确实是这样,
不过它所使用的默认值和我们想要的相去甚远(实际上,甚至比随机打的还
要差——无数个猴子在GNU emacs里打字永远不会创造出一个好程序)
(译注:请参考Infinite Monkey Theorem)
 
So, you can either get rid of GNU emacs, or change it to use saner
values.  To do the latter, you can stick the following in your .emacs file:
所以你要么放弃GNU emacs,要么改变它让它使用更合理的设定。要采用
后一个方案,你可以把下面这段粘贴到你的.emacs文件里。
(defun c-lineup-arglist-tabs-only (ignored)
  "Line up argument lists by tabs, not spaces"
  (let* ((anchor (c-langelem-pos c-syntactic-element))
(column (c-langelem-2nd-pos c-syntactic-element))
(offset (- (1+ column) anchor))
(steps (floor offset c-basic-offset)))
    (* (max steps 1)
       c-basic-offset)))

(add-hook 'c-mode-common-hook
          (lambda ()
            ;; Add kernel style
            (c-add-style
             "linux-tabs-only"
             '("linux" (c-offsets-alist
                        (arglist-cont-nonempty
                         c-lineup-gcc-asm-reg
                         c-lineup-arglist-tabs-only))))))

(add-hook 'c-mode-hook
          (lambda ()
            (let ((filename (buffer-file-name)))
              ;; Enable kernel mode for the appropriate files
              (when (and filename
                         (string-match (expand-file-name "~/src/linux-trees")
                                       filename))
                (setq indent-tabs-mode t)
                (c-set-style "linux-tabs-only")))))
 
This will make emacs go better with the kernel coding style for C
files below ~/src/linux-trees.
 
But even if you fail in getting emacs to do sane formatting, not
everything is lost: use "indent".
这样就定义了M-x linux-c-mode命令。当你hack一个模块的时候,如果你把字符串
-*- linux-c -*-放在头两行的某个位置,这个模式将会被自动调用。如果你希望在你修改
/usr/src/linux里的文件时魔术般自动打开linux-c-mode的话,你也可能需要添加
 
Now, again, GNU indent has the same brain-dead settings that GNU emacs
has, which is why you need to give it a few command line options.
However, that's not too bad, because even the makers of GNU indent
recognize the authority of K&R (the GNU people aren't evil, they are
just severely misguided in this matter), so you just give indent the
options "-kr -i8" (stands for "K&R, 8 character indents"), or use
"scripts/Lindent", which indents in the latest style.
不过,GNU indent也有和GNU emacs一样有问题的设定,所以你需要给它一
些命令选项。不过,这还不算太糟糕,因为就算是GNU indent的作者也认同K&R
的权威性(GNU的人并不是坏人,他们只是在这个问题上被严重的误导了),
所以你只要给indent指定选项“- kr -i8”(代表“K&R,8个字符缩进”),或者使
用“scripts/Lindent”,这样就可以以最时髦的方式缩进源代码。“indent”有很多
选项,特别是重新格式化注释的时候,你可能需要看一下它的手册页。不过记
住:“indent”不能修正坏的编程习惯。
 
"indent" has a lot of options, and especially when it comes to comment
re-formatting you may want to take a look at the man page.  But
remember: "indent" is not a fix for bad programming.
不过就算你尝试让emacs正确的格式化代码失败了,也并不意味着你失去了
一切:还可以用“indent”。

 
 
Chapter 10: Kconfig configuration files
第十章:Kconfig配置文件
For all of the Kconfig* configuration files throughout the source tree,
the indentation is somewhat different.  Lines under a "config" definition
are indented with one tab, while help text is indented an additional two
spaces.  Example:
对于遍布源码树的所有Kconfig*配置文件来说,它们缩进方式与C代码相比
有所不同。紧挨在“config”定义下面的行缩进一个制表符,帮助信息则再多
缩进2个空格。比如:
config AUDIT
	bool "Auditing support"
	depends on NET
	help
	  Enable auditing infrastructure that can be used with another
	  kernel subsystem, such as SELinux (which requires this for
	  logging of avc messages output).  Does not do system-call
	  auditing without CONFIG_AUDITSYSCALL.
 
Features that might still be considered unstable should be defined as
dependent on "EXPERIMENTAL":
仍然被认为不够稳定的功能应该被定义为依赖于“EXPERIMENTAL”:
config SLUB
	depends on EXPERIMENTAL && !ARCH_USES_SLAB_PAGE_STRUCT
	bool "SLUB (Unqueued Allocator)"
	...
 
while seriously dangerous features (such as write support for certain
filesystems) should advertise this prominently in their prompt string:
而那些危险的功能(比如某些文件系统的写支持)应该在它们的提示字符串里显著的声明这一点:
config ADFS_FS_RW
	bool "ADFS write support (DANGEROUS)"
	depends on ADFS_FS
	...
 
For full documentation on the configuration files, see the file
Documentation/kbuild/kconfig-language.txt.
要查看配置文件的完整文档,请看Documentation/kbuild/kconfig-language.txt。
 
Chapter 11: Data structures
第十一章:数据结构
 
Data structures that have visibility outside the single-threaded
environment they are created and destroyed in should always have
reference counts.  In the kernel, garbage collection doesn't exist (and
outside the kernel garbage collection is slow and inefficient), which
means that you absolutely _have_ to reference count all your uses.
如果一个数据结构,在创建和销毁它的单线执行环境之外可见,那么它
必须要有一个引用计数器。内核里没有垃圾收集(并且内核之外的垃圾
收集慢且效率低下),这意味着你绝对需要记录你对这种数据结构的使
用情况。
 
Reference counting means that you can avoid locking, and allows multiple
users to have access to the data structure in parallel - and not having
to worry about the structure suddenly going away from under them just
because they slept or did something else for a while.
引用计数意味着你能够避免上锁,并且允许多个用户并行访问这个数据
结构——而不需要担心这个数据结构仅仅因为暂时不被使用就消失了,
那些用户可能不过是沉睡了一阵或者做了一些其他事情而已。
 
Note that locking is _not_ a replacement for reference counting.
Locking is used to keep data structures coherent, while reference
counting is a memory management technique.  Usually both are needed, and
they are not to be confused with each other.
注意上锁不能取代引用计数。上锁是为了保持数据结构的一致性,而引
用计数是一个内存管理技巧。通常二者都需要,不要把两个搞混了。
 
Many data structures can indeed have two levels of reference counting,
when there are users of different "classes".  The subclass count counts
the number of subclass users, and decrements the global count just once
when the subclass count goes to zero.
很多数据结构实际上有2级引用计数,它们通常有不同“类”的用户。子类计
数器统计子类用户的数量,每当子类计数器减至零时,全局计数器减一。
 
Examples of this kind of "multi-level-reference-counting" can be found in
memory management ("struct mm_struct": mm_users and mm_count), and in
filesystem code ("struct super_block": s_count and s_active).
这种“多级引用计数”的例子可以在内存管理(“struct mm_struct”:mm_users和
mm_count)和文件系统(“struct super_block”:s_count和s_active)中找到。
 
Remember: if another thread can find your data structure, and you don't
have a reference count on it, you almost certainly have a bug.
记住:如果另一个执行线索可以找到你的数据结构,但是这个数据结构没有
引用计数器,这里几乎肯定是一个bug。
 
 
Chapter 12: Macros, Enums and RTL
 第十二章:宏,列举(enum)和RTL
 
Names of macros defining constants and labels in enums are capitalized.
定义常量和列举里的标签的宏的名字需要大写。
#define CONSTANT 0x12345
 
Enums are preferred when defining several related constants.
在定义几个相关的常量时,最好用列举。
 
CAPITALIZED macro names are appreciated but macros resembling functions
may be named in lower case.
宏的名字请用大写字母,不过形如函数的宏的名字可以用小写字母。
 
Generally, inline functions are preferable to macros resembling functions.
一般的,如果能写成内联函数就不要写成像函数的宏。
 
Macros with multiple statements should be enclosed in a do - while block:
含有多个语句的宏应该被包含在一个do-while代码块里:
#define macrofun(a, b, c) 			\
	do {					\
		if (a == 5)			\
			do_this(b, c);		\
	} while (0)
 
Things to avoid when using macros:
使用宏的时候应避免的事情:
 
1) macros that affect control flow:
1) 影响控制流程的宏:
#define FOO(x)					\
	do {					\
		if (blah(x) < 0)		\
			return -EBUGGERED;	\
	} while(0)
 
is a _very_ bad idea.  It looks like a function call but exits the "calling"
function; don't break the internal parsers of those who will read the code.
非常不好。它看起来像一个函数,不过却能导致“调用”它的函数退出;
don't break the internal parsers of those who will read the code.
 
2) macros that depend on having a local variable with a magic name:
2) 依赖于一个固定名字的本地变量的宏:
#define FOO(val) bar(index, val)
 
might look like a good thing, but it's confusing as hell when one reads the
code and it's prone to breakage from seemingly innocent changes.
可能看起来像是个不错的东西,不过它非常容易把读代码的人搞糊涂,而且
容易导致看起来不相关的改动带来错误。
 
3) macros with arguments that are used as l-values: FOO(x) = y; will
bite you if somebody e.g. turns FOO into an inline function.
3) 作为左值的带参数的宏: FOO(x) = y;如果有人把FOO变成一个内联
函数的话,这种用法就会出错了。
 
4) forgetting about precedence: macros defining constants using expressions
must enclose the expression in parentheses. Beware of similar issues with
macros using parameters.
4) 忘记了优先级:使用表达式定义常量的宏必须将表达式置于一对小括号之内。
带参数的宏也要注意此类问题。
#define CONSTANT 0x4000
#define CONSTEXP (CONSTANT | 3)
 
 
The cpp manual deals with macros exhaustively. The gcc internals manual also
covers RTL which is used frequently with assembly language in the kernel.
cpp手册对宏的讲解很详细。Gcc internals手册也详细讲解了RTL(译注:register
transfer language),内核里的汇编语言经常用到它。
 
 
Chapter 13: Printing kernel messages
 第十三章:打印内核消息
 
Kernel developers like to be seen as literate. Do mind the spelling
of kernel messages to make a good impression. Do not use crippled
words like "dont"; use "do not" or "don't" instead.  Make the messages
concise, clear, and unambiguous.
内核开发者应该是受过良好教育的。请一定注意内核信息的拼写,以给人
以好的印象。不要用不规范的单词比如“dont”,而要用“do not”或者“don't”。
保证这些信息简单、明了、无歧义。
 
Kernel messages do not have to be terminated with a period.
内核信息不必以句点结束。
 
Printing numbers in parentheses (%d) adds no value and should be avoided.
在小括号里打印数字(%d)没有任何价值,应该避免这样做。
 
There are a number of driver model diagnostic macros in <linux/device.h>
which you should use to make sure messages are matched to the right device
and driver, and are tagged with the right level:  dev_err(), dev_warn(),
dev_info(), and so forth.  For messages that aren't associated with a
particular device, <linux/printk.h> defines pr_debug() and pr_info().
<linux/device.h>里有一些驱动模型诊断宏,你应该使用它们,以确保信息
对应于正确的设备和驱动,并且被标记了正确的消息级别。这些宏有:
dev_err(), dev_warn(),dev_info()等等。对于那些不和某个特定设备相关连的
信息,<linux/kernel.h>定义了pr_debug()和pr_info()。
 
Coming up with good debugging messages can be quite a challenge; and once
you have them, they can be a huge help for remote troubleshooting.  Such
messages should be compiled out when the DEBUG symbol is not defined (that
is, by default they are not included).  When you use dev_dbg() or pr_debug(),
that's automatic.  Many subsystems have Kconfig options to turn on -DDEBUG.
A related convention uses VERBOSE_DEBUG to add dev_vdbg() messages to the
ones already enabled by DEBUG.
写出好的调试信息可以是一个很大的挑战;当你写出来之后,这些信息在远程除错
的时候就会成为极大的帮助。当DEBUG符号没有被定义的时候,这些信息不应该被
编译进内核里(也就是说,默认地,它们不应该被包含在内)。如果你使用dev_dbg()
或者pr_debug(),就能自动达到这个效果。很多子系统拥有Kconfig选项来启用-DDEBUG。
还有一个相关的惯例是使用VERBOSE_DEBUG来添加dev_vdbg()消息到那些已经由DEBUG
启用的消息之上。
 
 
Chapter 14: Allocating memory
第十四章:分配内存
 
The kernel provides the following general purpose memory allocators:
kmalloc(), kzalloc(), kcalloc(), vmalloc(), and vzalloc().  Please refer to
the API documentation for further information about them.
内核提供了下面的一般用途的内存分配函数:kmalloc(),kzalloc(),kcalloc()和
vmalloc()。请参考API文档以获取有关它们的详细信息。

 
The preferred form for passing a size of a struct is the following:
传递结构体大小的首选形式是这样的:
p = kmalloc(sizeof(*p), ...);
 
The alternative form where struct name is spelled out hurts readability and
introduces an opportunity for a bug when the pointer variable type is changed
but the corresponding sizeof that is passed to a memory allocator is not.
另外一种传递方式中,sizeof的操作数是结构体的名字,这样会降低可读性,并且
可能会引入bug。有可能指针变量类型被改变时,而对应的传递给内存分配函数的
sizeof的结果不变。

Casting the return value which is a void pointer is redundant. The conversion
from void pointer to any other pointer type is guaranteed by the C programming
language.
强制转换一个void指针返回值是多余的。C语言本身保证了从void指针到其他任何
指针类型的转换是没有问题的。
 
 
Chapter 15: The inline disease
第十五章:内联弊病

There appears to be a common misperception that gcc has a magic "make me
faster" speedup option called "inline". While the use of inlines can be
appropriate (for example as a means of replacing macros, see Chapter 12), it
very often is not. Abundant use of the inline keyword leads to a much bigger
kernel, which in turn slows the system as a whole down, due to a bigger
icache footprint for the CPU and simply because there is less memory
available for the pagecache. Just think about it; a pagecache miss causes a
disk seek, which easily takes 5 milliseconds. There are a LOT of cpu cycles
that can go into these 5 milliseconds.
有一个常见的误解是内联函数是gcc提供的可以让代码运行更快的一个选项。
虽然使用内联函数有时候是恰当的(比如作为一种替代宏的方式,请看第十二章),
不过很多情况下不是这样。inline关键字的过度使用会使内核变大,从而使整个系
统运行速度变慢。因为大内核会占用更多的指令高速缓存(译注:一级缓存通常是
指令缓存和数据缓存分开的)而且会导致pagecache的可用内存减少。想象一下,
一次pagecache未命中就会导致一次磁盘寻址,将耗时5毫秒。5毫秒的时间内CPU
能执行很多很多指令。
 
A reasonable rule of thumb is to not put inline at functions that have more
than 3 lines of code in them. An exception to this rule are the cases where
a parameter is known to be a compiletime constant, and as a result of this
constantness you *know* the compiler will be able to optimize most of your
function away at compile time. For a good example of this later case, see
the kmalloc() inline function.
一个基本的原则是如果一个函数有3行以上,就不要把它变成内联函数。这个原
则的一个例外是,如果你知道某个参数是一个编译时常量,而且因为这个常量你
确定编译器在编译时能优化掉你的函数的大部分代码,那仍然可以给它加上inline
关键字。kmalloc()内联函数就是一个很好的例子。
 
Often people argue that adding inline to functions that are static and used
only once is always a win since there is no space tradeoff. While this is
technically correct, gcc is capable of inlining these automatically without
help, and the maintenance issue of removing the inline when a second user
appears outweighs the potential value of the hint that tells gcc to do
something it would have done anyway.
人们经常主张给static的而且只用了一次的函数加上inline,不会有任何损失,
因为这种情况下没有什么好权衡的。虽然从技术上说,这是正确的,不过gcc可
以在没有提示的情况下自动使其内联,而且其他用户可能会要求移除inline,此
种维护上的争论会抵消可以告诉gcc来做某些事情的提示带来的潜在价值。不管
有没有inline,这种函数都会被内联。
 
 
Chapter 16: Function return values and names
第十六章:函数返回值及命名

Functions can return values of many different kinds, and one of the
most common is a value indicating whether the function succeeded or
failed.  Such a value can be represented as an error-code integer
(-Exxx = failure, 0 = success) or a "succeeded" boolean (0 = failure,
non-zero = success).
函数可以返回很多种不同类型的值,最常见的一种是表明函数执行成功或
者失败的值。这样的一个值可以表示为一个错误代码整数(-E**=失败,
0=成功)或者一个“成功”布尔值(0=失败,非0=成功)。
 
Mixing up these two sorts of representations is a fertile source of
difficult-to-find bugs.  If the C language included a strong distinction
between integers and booleans then the compiler would find these mistakes
for us... but it doesn't.  To help prevent such bugs, always follow this
convention:
混合使用这两种表达方式是难于发现的bug的来源。如果C语言本身严格区分整
形和布尔型变量,那么编译器就能够帮我们发现这些错误……不过C语言不区分。
为了避免产生这种bug,请遵循下面的惯例:
 
If the name of a function is an action or an imperative command,
the function should return an error-code integer.  If the name
is a predicate, the function should return a "succeeded" boolean.
如果函数的名字是一个动作或者强制性的命令,那么这个函数应该返回错误代码
整数。如果是一个判断,那么函数应该返回一个“成功”布尔值 。
 
For example, "add work" is a command, and the add_work() function returns 0
for success or -EBUSY for failure.  In the same way, "PCI device present" is
a predicate, and the pci_dev_present() function returns 1 if it succeeds in
finding a matching device or 0 if it doesn't.
比如,“add work”是一个命令,所以add_work()函数在成功时返回0,在失败时返
回-EBUSY。类似的,因为“PCI device present”是一个判断,所pci_dev_present()函
数在成功找到一个匹配的设备时应该返回1,如果找不到时应该返回0。
 
All EXPORTed functions must respect this convention, and so should all
public functions.  Private (static) functions need not, but it is
recommended that they do.
所有导出(译注:EXPORT)的函数都必须遵守这个惯例,所有的公共函数也
都应该如此。私有(static)函数不需要如此,但是我们也推荐这样做。
 
Functions whose return value is the actual result of a computation, rather
than an indication of whether the computation succeeded, are not subject to
this rule.  Generally they indicate failure by returning some out-of-range
result.  Typical examples would be functions that return pointers; they use
NULL or the ERR_PTR mechanism to report failure.
返回值是实际计算结果而不是计算是否成功的标志的函数不受此惯例的限制。
一般的,他们通过返回一些正常值范围之外的结果来表示出错。典型的例子
是返回指针的函数,他们使用NULL或者ERR_PTR机制来报告错误。
 
 
Chapter 17:  Don't re-invent the kernel macros
 第十七章:不要重新发明内核宏
 
The header file include/linux/kernel.h contains a number of macros that
you should use, rather than explicitly coding some variant of them yourself.
For example, if you need to calculate the length of an array, take advantage
of the macro
头文件include/linux/kernel.h包含了一些宏,你应该使用它们,而不要自己写
一些它们的变种。比如,如果你需要计算一个数组的长度,使用这个宏
 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
 
Similarly, if you need to calculate the size of some structure member, use
类似的,如果你要计算某结构体成员的大小,使用
 #define FIELD_SIZEOF(t, f) (sizeof(((t*)0)->f))
 
There are also min() and max() macros that do strict type checking if you
need them.  Feel free to peruse that header file to see what else is already
defined that you shouldn't reproduce in your code.
还有可以做严格的类型检查的min()和max()宏,如果你需要可以使用它们。你
可以自己看看那个头文件里还定义了什么你可以拿来用的东西,如果有定义的
话,你就不应在你的代码里自己重新定义。
 
 
Chapter 18:  Editor modelines and other cruft
第十八章:编辑器模式行和其他需要罗嗦的事情
 
Some editors can interpret configuration information embedded in source files,
indicated with special markers.  For example, emacs interprets lines marked
like this:
有一些编辑器可以解释嵌入在源文件里的由一些特殊标记标明的配置信息。
比如,emacs能够解释被标记成这样的行:
-*- mode: c -*-
 
 
Or like this:
或者这样的:
/*
Local Variables:
compile-command: "gcc -DMAGIC_DEBUG_FLAG foo.c"
End:
*/
 
Vim interprets markers that look like this:
Vim能够解释这样的标记:
/* vim:set sw=8 noet */
 
Do not include any of these in source files.  People have their own personal
editor configurations, and your source files should not override them.  This
includes markers for indentation and mode configuration.  People may use their
own custom mode, or may have some other magic method for making indentation
work correctly.
不要在源代码中包含任何这样的内容。每个人都有他自己的编辑器配置,你的源文
件不应该覆盖别人的配置。这包括有关缩进和模式配置的标记。人们可以使用他们
自己定制的模式,或者使用其他可以产生正确的缩进的巧妙方法。
 
 
 
Appendix I: References
 
The C Programming Language, Second Edition
by Brian W. Kernighan and Dennis M. Ritchie.
Prentice Hall, Inc., 1988.
ISBN 0-13-110362-8 (paperback), 0-13-110370-9 (hardback).
URL: http://cm.bell-labs.com/cm/cs/cbook/
 
The Practice of Programming
by Brian W. Kernighan and Rob Pike.
Addison-Wesley, Inc., 1999.
ISBN 0-201-61586-X.
URL: http://cm.bell-labs.com/cm/cs/tpop/
 
GNU manuals - where in compliance with K&R and this text - for cpp, gcc,
gcc internals and indent, all available from http://www.gnu.org/manual/
 
WG14 is the international standardization working group for the programming
language C, URL: http://www.open-std.org/JTC1/SC22/WG14/
 
Kernel CodingStyle, by greg@kroah.com at OLS 2002:
http://www.kroah.com/linux/talks/ols_2002_kernel_codingstyle_talk/html/
 

1.4.8 异常处理错误报告
      在上一节里面我们我们诊断贺消除程序运行中的严重错误。然而,那些非致命的错误上一节的做法就太激进了。你的程序自己对待一些特殊方式有克服、忽视或者处理等办法。例如,如果一个图形应用程序不能打开一个弹出的文件浏览器选择了一个文件,你通常不希望中止整个应用程序。相反,你可能想找出是什么问题,然后进行处理,可以弹出出了一个对话框或者让你点击文件浏览器中的“取消”按钮。
      人们往往希望在不同错误类型的时候用异常处理来进行补救。(GLib的在线手册使用的术语的错误报告)传统的C风格的函数,在测试调用结束以后返回特殊的错误代码,一些功能提供了额外的细节(例如,通过errno这个全局变量)。而C++和Java这类高级语言提供了一种特殊的语法来进行异常处理,像try{}, throw(), catch(){}.
      GLib中的异常处理没有像那样复杂的功能,因为GLib用的是C。但是它确实提供了一个称为GError系统,比自己重新动手写一个C的方法要容易使用的多。GError的数据结构是核心:你如何使用这种数据结构和它的实施是同样重要的。
 
      GError和GError函数
      使用GError的函数都把GError指针的地址作为最后一个参数。如果你想用一个GError的变量err要定义为GError *err,传递的时候用&err。此外你要设置指针的值为0。你还可以指定这个参数为NULL,这时该函数将禁用错误报告。
      一个GError结构体有以下字段:
domain(GQuark类型):一个标签代表模块或者子系统错误,域名或者类的错误。每一个错误域必须有一个宏定义的格式 PREFIX_MODULE_ERROR(例如,G_FILE_ERROR)。宏扩展到夸克的数值的形式返回。
code(gint类型):错误代码;这就是错误域内的特定错误。每一个可能的错误代码需要一个相应的符号称为PrefixModuleError枚举类型的形式PREFIX_MODULE_ERROR_CODE(例如,在GFileError G_FILE_ERROR_TYPE)。
message(gchar*类型):用通俗易懂的语言完整描述一个错误。
 
      下面的代码片段演示了如何从一个函数的错误条件(do_something())使用GError:
 GError *error = NULL;

      /* use this GError variable as last argument */
      do_something(arg1, arg2, &error);

      /* was there an error? */
      if (error != NULL)
      {
	/* report the message */
  	g_printerr("Error when doing something: %s\n", error->message);

  	/* free error structure */
  	g_error_free(error);
      }

 

 你可以看到这个代码需要你完成后用g_error_free()释放GError。因此,如果你提供一个GError参数的函数,你应该经常检查,否则,你的内存有泄漏的危险。
      如果你想要做报告错误以外的东西,你可能想知道错误的domain和code。而不是用手工去检查,你应该使用g_error_matches()函数匹配错误中的domain和code。第一个参数是GError结构,第二个是错误的domain,第三个是一个特定的错误code。如果错误匹配domain和code,该函数返回TRUE,否则返回FALSE。下面是一个例子:
	GError *error = NULL;
	gchar *filename;
	BluesGuitar *fender, *bender;

	<< .. >>
	
	filename = blues_improvise(fender, BLUES_A_MAJOR, &error);
	if (error != NULL)
	{
		/* see if the expensive guitar is broken */
  		if (g_error_matches(error, BLUES_GUITAR_ERROR, BLUES_GUITAR_ERROR_BROKEN))
  		{
			/* if so, try the cheap guitar */
    			g_clear_error(&error);
    			filename = blues_improvise(bender, BLUES_A_MAJOR, &error);
  		}
	}

	/* if nothing's working, default to Clapton */
	if (error != NULL)
	{
		filename = g_strdup("clapton-1966.wav");
  		g_error_free(error);
	}

	blues_play(filename);

 

 

  在这个例子中,函数blues_improvise()运行没有问题的情况下后返回一个文件名。如果出现错误,程序就检查错误code BLUES_GUITAR_ERROR_BROKEN在domain BLUES_GUITAR_ERROR里么。如果是这个问题,程序就尝试不同时间的不同参数。试图在此之前,它会调用g_clear_error()来清除错误,这个函数释放GError结构体并且把指针重置为NULL。
      如果有错误的东西,这个第二次尝试后,显示的东西仍然无法正常工作的权利,该方案放弃了。而不是试图再blues_improvise()调用,它使用默认的文件名(“clapton-1966.wav”),所以blues_play()可以做它的事。
      警告:
您使用GError*结构后,应该立即释放并复位指针。 GError启用的功能可以不使用相同的指针,但是在同一时间的几个错误,只有一个错误的空间。如前所述,如果你不释放GError内存​​,你的程序将有内存泄漏。
 
      定义自己的错误条件
      要在自己的函数里面使用GError系统来报告错误,要做到以下几点:
      1.创造一个适当命名的宏,扩展到一个唯一的GQuark值定义错误domain。
      2.定义一个枚举类型的所有错误code。
      3.在你的函数里面增加一个GError**类型的参数(也就是说,这个参数是一个指针到GError结构的指针)。如果该函数使用可变参数,这个参数要在va_args(...)之前.
      4.在你的函数检测到一个错误的地方,创建一个新的GError结构,并填写在相应的情况。
 
 
      这里是一个错误的域名和一些代码的定义:
 /* define the error domain */
      #define MAWA_DOSOMETHING_ERROR (mawa_dosomething_error_quark())
      
      GQuark mawa_dosomething_error_quark(void)
      {
        static GQuark q = 0;
	if (q == 0)
	{
		q = g_quark_from_static_string("mawa-dosomething-error");
	}
  	return(q);
      }

      /* and the error codes */
      typedef enum {
       MAWA_DOSOMETHING_ERROR_PANIC,
       MAWA_DOSOMETHING_ERROR_NO_INPUT,
       MAWA_DOSOMETHING_ERROR_INPUT_TOO_BORING,
       MAWA_DOSOMETHING_ERROR_FAILED   /* abort code */
      }

 

  前面这个例子要注意的是mawa_dosomething_error_quark()。它创建新的错误域夸克如果不存在,但一个静态变量q的结果就没有改变,所以,它并没有执行任何额外的计算连续调用。
      这个片段说明了如何使用新的域名和代码:
 void mawa_dosomething_simple(GError **error)
      {     
        gint i;
	gboolean it_worked;

	<< do something that sets it_worked to TRUE or FALSE >>

	if (!it_worked)
	{
		g_set_error(error, MAWA_DOSOMETHING_ERROR,
		            MAWA_DOSOMETHING_ERROR_PANIC,
			    "Panic in do_something_simple(), i = %d", i);
	}
      }

 

 这个函数“做了一些事情”如果失败。调用g_set_error()来设置返回的错误结果。此函数作为第一个参数错误指针的地址,如果不为NULL,则设置了一个新分配的GError结构的指针。 g_set_error()函数填充这个结构的第三个和第四个参数(错误域和码),其余的参数的printf()格式字符串和参数列表,成为GError的消息字段。
      如果你想从另一个函数使用错误代码,您需要特别小心:
 void mawa_dosomething_nested(GError **error)
      {
	gint i;
      	gboolean it_worked;
  	GError *simple_error = NULL;

  	<< do something >>

  	if (!it_worked)
  	{
		g_set_error(error,
			MAWA_DOSOMETHING_ERROR,
                	MAWA_DOSOMETHING_ERROR_PANIC,
                	"Panic in do_something_nested(), i = %d", i);
    		return;
  	}

  	do_something_simple(&simple_error);
  	if (simple_error != NULL)
  	{

		<< additional error handling >>

        	g_propagate_error(error, simple_error);
  	}
      }

 

 mawa_dosomething_nested()中,如果该函数的第一部分失效,那么类似的错误初始化会出现。然而,如果第一部分正常工作的话,这个函数会继续下去并调用do_something_simple()。因为该函数能够设置一个错误条件,使发送给最初调用者的错误条件有意义。为了做到这一点,这个函数首先收集do_something_simple()条件到simple_error,然后调用g_propagate_error()从simple_error到error传递GError结构体。
      警告:
不能将作为参数得到的GError **作为指针传递给任何其他函数。如果GError **为NULL,当你试图引用它时,你的程序将会崩溃。
 
      如果要发送从别的函数得到的错误到一些其他地方,使用g_propagate_error(error_dest, error_src)在这里,error_dest是GError **的错误的目的地(destination),error_src是GError **的源(source)。如果目的地(destination)不为NULL,该函数会简单地将源(source)复制给目的地(destination)。然而,如果目的地(destination)实际为NULL,该函数会释放源错误(source error)。
      你现在可能已经注意到,GError力图通过对NULL的重视实现透明度,所以当你不关心错误的特定性质时,你不必担心内存泄露和额外的GError指针。另外,如果你的函数中的一个遇见作为错误条件的NULL,你可以将“用户不希望任何特定的错误处理,也许是想该函数可以尽可能的修补问题”作为一个提示。
      你需要时刻记住,仅当你坚持规则时,GError是一种相当可行的处理异常的工具

 

  中秋了。小师弟小师妹也快军训完了吧。记得之前每次毕业的时候bbs肯定都有师兄来写点人生指南阿,实习感慨阿。结果我进来以后bbs没落了,今年我们都毕业2个月了,没见一篇。虽然我对学习了四年的软件学院颇有意见。但是,什么叫母校呢。就是你可以一天操她百遍,却不许他人说她一句的地方。所以,我来写一篇云南大学软件学院入学全指南。(此处略停顿,等待掌声^_^)
  选择
  除了软件学院,你可以选择的更多。
  我知道,在经历了天朝12年的应试教育以后。来大学第一天的感觉就是”我自由了“。这是一个最好的感觉,也是一个最坏的感觉。好是,你确实可以自己决定很多事情了,坏是,你确定你做出一些比较有利的决定么。上大学以后你确实知道你想要什么么。如果我这样问你,你能回答么。天朝的具体制度不去评论。我们以生物最基本的要求来要求自己,就是生存下去。^_^,是不是你说你都活了二十左右年了。难道不是生存么。我想说的是依靠自己的生存,有爸是双江,也蛮好的阿。是官二代,富二代。那我觉得,你的生存问题也不大。做一个好人就好了。大多数师弟师妹,你们应该好好考虑一下4年以后,你能怎么活下去。
  过于明确的功利性选择向为有识之士所不齿,但是,如果当一个刚刚走出校门,面对残酷的商业竞争而举足失措的少年,或者是对于一个几乎耗尽了他的家庭生存成本终于读完了大本,却在就业市场上徒劳往返,不断奔波碰壁,甚至连最基本的生存都已难以保证的失败者,我们再义正辞严的宣讲什么 “君子谋道不谋食”,再讲什么“君子喻于义”,再讲什么“一箪食,一瓢饮,在陋巷。人不堪其忧,回也不改其乐”,这虽然省心省力,而且占据了道德的制高点,却未免有点不厚道。(抄的,我写不出这么好,但是表达了我想表达的东西。)
  做IT很辛苦。加班就是必备技能。所以,我说除了软件学院,你可以有很多的选择。也许你听说IT高工资,但是我说,高工资全部在一线大公司,一线大公司,全部在一线城市。一线城市的幸福感真的很低。你那高工资跟那房价一比,跟工作压力一比。觉得真的一般。而如果你回家,考了公务员或者银行或者垄断国企,我觉得你生活质量和幸福感会很好。所以,人各有志,最好自己客观的评价一下自己,适合做什么事情。想过什么样的生活。好有一个全局的打算。将来是可以公务员可以事业单位可以国企可以垄断行业。所以,除了IT有很多选择。
  软件学院后面两年的学费是1w2。不去说,值不值得的问题,如果家里条件有困难的同学。先思考,自己是不是真的喜欢做软件这个行当,有没有大毅力学这个行当。如果答案是否的话。我建议换专业。(过多不评价)
  最后说一说,女生读软件的问题。我到目前为止,只听说过一个云南大学软件学院的大神是女的。而且是05级的。也就是说,我读了4年,向前认识2界向后认识2界,一共5界中。只听说过一个女生写程序很猛。所以,女生学软件,请慎重考虑。(完全没有歧视的意思)好了,说了不好的。来说说好的。女生么,大学一般学习比较乖,成绩一般比较好。我们院长关系还是很硬。每年都有去北大和复旦,其他也有。比例是5%。就是说。100个人里面保研5个。如果你能保持第一名三年。恭喜你,你可以去北大了。所以,对于女生的建议,要不就转专业,要不就好好学习。
  综上所述,我只想说,除了搞IT,你还有很多的选择,让自己的人生更加的美好。
  好了,说了怎么多。你最后还是决定留下来读这个该死的软件学院的话。你可以继续接着看了。
 
  做为一个软件开发人员,或者说做为一个计算机专业的学生。我在下面三个选择中不断徘徊,不断挣扎。就为了这三个问题,耽误了很多学习的时间。所以,我有必要将我在这三个问题获得的经验分享一下。(PS:就算你不是电脑专业的也可以有些收获)
   第一个选择和第二个选择密切相关:你需要一台什么样的电脑和你需要用什么样的操作系统。
   你如果对这个问题觉得很奇怪了,或者说你要说你觉得电脑只有价钱的区分,操作系统只有windows xp 到 windows vista 到 windows 7。那我建议你多了解一下外面的世界。这情况是微软的成功,中国软件的悲哀。
   扯远了,拉回来。我们为什么要选择电脑(品牌)。作为一个开发人员,我首先推荐ThinkPad。理由只有一个,专业。
  作为一个企业或者一个开发人员来说,稳定是最重要的。根据我的观察和使用情况。即使Thinkpad已经被联想收购了,但是依然是最好的laptop没有之一。这个不解释。
 
  另外一个选择,Mac。具体详细见
  http://tiny4.org/blog/2010/02/why-programmers-should-use-mac-os-x/
  再次不解释。
  PS:不解释是因为有共鸣者不用解释,不了解者应该会自己Google一下。
          彪悍的人生不需要解释。——老罗
 
  所以,如果你在纠结于买一个什么样子的电脑,果断Thinkpad or macbook 。也许你说预算不够买thinkpad或者是macbook。那我就觉得,你可以入一个thinkpad二手。3000左右的,性能足够满足你所有学习上的需求。不过记住,thinkpad我只推荐T系列和X系列。其他的看都没有看过。
 
  第二,选择什么样的操作系统。Windows or Linux or Unix or MacOS。
  记得我最牛x的一个老师(伯克利数学博士)跟我说过,如果你选择windows你就站在整个硅谷的对面。呵呵,我对此深信不疑。首先,我很尊重微软。他是一个伟大的公司,为我们提供了无数伟大的技术。可是,我不喜欢。我更喜欢Linux这样自由免费的系统。具体理由详见:
  http://blog.renren.com/blog/232813290/404956937
  呵呵,如果你能坚持看完,那你就应该知道我的推荐了:
  Linux or Unix or MacOS 没有Windows
 
  第三,选择什么样的编程语言。
  曾经,我苦苦纠缠于要学习什么语言,以至于什么都没有学的很精通。今天站在这个语言阵营,明天为那个语言阵营呐喊。结果三年以后的今天,我回过头去看。才发现,用什么语言都不重要,重要的是设计。不同的语言在不同的领域有不同的优点。只要能理解问题,设计出解决问题的方案。在找合适的语言来做就好了。前两天看见贺神转载的文章,上面有一句话很认同。手里如果有一把锤子,所有的问题都只有用钉钉子来解决。手里的工具是什么,就是你会什么语言。(这里大部分是转载了之前自己写的一篇blog,说这个有点早了,下面要开始说怎么学习了)
 
 
  学习
  先泛泛谈一下,在今天这个知识爆炸的时代。我们不可能学会所有东西。因此,只要有活到老学到老的决心就好。学海无涯。。。。。。。(每次有这种想法,就想起高爷爷(Donald Ervin Knuth)用一辈子写一部书,而天朝很难有人做技术到10年。浮躁的天朝)
  IT里面也分为很多方向,而不是仅仅学院里面开设的那四个。而且发现,其实后来找工作时候,用到的知识,都是自己学的。就是说,学院与业界还是存在一定的脱轨。这个无能为力。当初对自己的定位也很模糊,学的很杂,走了很多不必要的弯路,所以今天把我的一些糟粕去掉,说一些精华的东西。因为我信仰开源自由的精神,也才来分享自己的想法。
  恭喜你,你将获得第一个学习技能的重要提示——提问的智慧。
  http://bbs.csdn.net/IndexPage/SmartQuestion.aspx
  这个很重要,是学习的基本,是向人请教的规范。虽然只是一篇文章,但是我却说他是一项技能,值得你初期每次提问的时候,看一看。
  第二:学会使用搜索引擎。
  简单的说,就是你要学会Google,也许你觉得没什么学的。所以,现在你可以google一下google怎么用的问题哦。
  http://zh.wikipedia.org/wiki/RTFM
  这两个是基础,就想九九乘法表一样,应该时刻谨记。
  然后,还有一个要学的基础就是选择一个文本编译器: Vim or Emacs
   你现在选什么我也不知道,如果你不喜欢折腾就选Vim。如果,你觉得要变成一个手指的魔术师而且禁得住折腾的话就选择Emacs。但是必须深入的学习一个。
   关于他们:
   http://linuxtoy.org/archives/why-emacs-vim-good.html
   Vim资料:
   http://coolshell.cn/articles/5426.html
   http://www.vimer.cn/category/vim
   Emacs资料:
   http://emacser.com/
 
   能把所有连接都看完。坚持到现在,不错不错。我觉得你已经有超越我的迹象了。
  在一开始,我说过,我们学IT其实不管喜欢不喜欢,最后的结果是我们能依靠它去生存下去。所以现在我们来选择我们要学习的方向,贪多不烂。深入一个方向比浅尝辄止好。(不断自省)还有一个问题就是IT这个行业每时每刻都有新的技术出来。有时候,你看见了,听说了。或许很眼红。觉得自己应该可以学学。已我的经验来看,这样做往往学不到东西。只说一个理由,就是新技术可能门槛很高。要学好它下面的基础要牢靠。也许一页字的东西。其实涉及了方方面面。你要看懂。要学习N多基础的东西。所以用2年学下面我说的东西吧。
   第一,Vim or Emacs。(一辈子^_^)
   第二,英语。
   第三,数学。
   只推荐两本书:
   http://book.douban.com/subject/1320282/
   http://book.douban.com/subject/1231910/
   第四,C语言(在学习Vim or Emacs的时候,最好找一个懂的人教你们怎么用命令行编译c程序,推荐:王逍老师)
   http://book.douban.com/subject/1882483/ (3个月-4个月)
   http://book.douban.com/subject/4141733/ (这本有电子版的,自己google。 2个月)
   http://book.douban.com/subject/5333562/  (6个月)
   http://book.douban.com/subject/1139426/  (3个月)
   http://book.douban.com/subject/1885170/   (一年)
   这些书,我建议你一本一本买,然后一本看完了再进入下一本。
   综上所述,我觉得起码要学到大二结束。如果你天赋异禀。几个月就搞定了。那我觉得你应该,也知道怎么向后面学习了。跟着感觉走就好了。
   大三以后,我觉得你可以选择方向了,由于才粗学浅。很多方向我都只能给一个大概。
   第一:Windows方向:
   Windows我迄今看到的就做安全产品活的好一些。所以你需要学C++,Windows底下的汇编。学Windows核心编程。学Windows驱动。然后,你可能要写界面。你要学MFC or WPF。(Windows真的不懂。就只能到这里了。有大神补充了,我在加上。)
   第二:J2EE方向:
   国内最大的java应用是淘宝。你应该学S2SH, EJB,Maven。(个人觉得本科在学校这里就够了。有大神补充了,我在加上。)
   第三:IOS方向:
   iphone app很火。起工资也很高。如果你当初买了Macbook。强烈推荐你走这个路。能google到很多学习路线。大概就是objective-c 然后cocoa(有大神补充了,我在加上。)
   第四:android方向:
   火的一踏糊涂,今年我同学去了很多oppo。大概是Java->Android。(有大神补充了,我在加上。)
   第五:前端方向:
   工资高。福利好。工作压力小。说的就是这个阿。大概是css+javascript。然后是flash吧。现在flash做游戏的很多。(有大神补充了,我在加上。)
   第六:后端方向:
   python or php。豆瓣是纯python。python很多在招聘,python的话要求学django。大部分毕业能给到5k以上。php不清楚。(不过,很多要求还要会css和javascript。有大神补充了,我在加上。)
   第七:DBA方向。
   数据库。(彻底不懂,有大神补充了,我在加上)
   第八:ERP方向。
   学好J2EE就好了。(有大神补充了,我在加上)
   第九:游戏方向:
   C++ Lua。在深入一点的话看看OpenGL。(有大神补充了,我在加上)
   
 
   
 
 
    写这个,主要是初期给师弟师妹们少走一些弯路。多出几个大神。零零总总。居然写了一个下午。很多想法在写的时候又没有想出来。哎。。。。。希望对各位有所帮助。
   
  
   
 
   
 
 
  
  
 

1.4.7 调试功能

 

    GLib有几个东西让你在你的程序中找到bug。其中有两个宏定义是返回普通的声明。除了中断退出程序以外,能还记录在G_LOG_LEVEL_CRITICAL中的消息。因此你可以在你程序有可能出现问题的地方使用他们:
    g_return_if_reached() 记录一个重要的信息,用于没有返回值的函数。
    g_return_val_if_reached(val) 记录一个重要的信息,用于有返回值的函数,返回val。
 
    其他两个类似也很方便的宏:
    g_return_if_fail(test)
    g_return_val_if_fail(test, val)
    如果test失败,记录消息在日志里面并返回。你可以在GNOME的函数里看到在开始的地方用来检查参数的有效性。
 
    在基本的调试上有两个以上的宏被展开成——断言。断言是在一定的某些条件下必须保存程序中进程的任何状态:
    g_assert() 如果程序的返回参数是FALSE时候,跟g_error()一样暂停程序。 
 
    g_assert_not_reached()不需要参数,只是简单的停止有错误消息的程序。
 
    你会发现整本书和绝大部分的GNOME应用中使用断言。此外,绝大部分的GNOME平台库使用这些安全功能,防止不适当的参数被传入。
    如果你的程序不是很长,你可以禁用任何断言,你能设置编译参数G_DISABLE_ASSERT这个宏就行了(例子:-DG_DISABLE_ASSERT)。禁用所有的断言可以节约一小点处理器的时间,因为消除了测试(消除测试。。窘。。我也不太懂。。。反正就是编译的时候,省略了一些东西,提高了速度)。
 

dbus+policykit初级hello world应用

2011年8月23日 10:29

 

  因为Ylmf os要做的好用,操作习惯上更像windows。所以,跟其他发行版不一样。很多时候需要root权限的时候。我们的程序就自动的提升权限进行操作。以免用户输入密码。这样做也用弊端。如果一个恶意的程序获得了这个权限。就会造成很严重的后果。
  我们用dbus来进行这个权限的提升操作。其实dbus是一个消息总线。具体不展开来说。要了解请自行google。我们写了一个叫ylmfdbus的一个dbus服务。这个服务设计成可插拔的。如果我有一个新的程序中某一个模块需要在root权限下执行。就需要把这个功能抽象的写成一个GObject类。然后注册到ylmfdbus里面。我们就可以通过调用dbus的方法来在root下执行这个函数,或者模块。在执行这个被提权的函数时候,为了防止被恶意操作,我们加入policykit验证,在每次GObject方法调用的时候现调用policykit。
 
接下来,我们开始进行打怪升级。
我们现看看已有装备:
[Dbus相关]
ylmf-dbus-global-defines.h
com.ylmf.DBusGlib.conf
com.ylmf.DBusGlib.service
service-main.c
[PolicyKit相关]
policykit-ylmf.c 
policykit-ylmf.h
com.ylmf.PolicyKit.policy 
[Makefile]
Makefile
 
com.ylmf.DBusGlib.service这个文件很简单。就配置了一下我们服务的路径。这里已经配置好了,不用去管它。这件装备已经是顶级了。
com.ylmf.DBusGlib.conf这个文件是dbus服务的配置文件。如果我们需要添加了新的对象。要在里面配置一个新的路径。这个路径的作用是跟namespace的作用一样的。这里约定用com.ylmf.dbus.xxxx来命名。也就是说,如果你只是添加了一个对象。这个文件只用添加一行。说明路径就好了。
ylmf-dbus-global-defines.h用来宏定义的公共文件,每一个对象要定义两个东西。一个是object path,一个是object interface。也只有一个地方用到在main函数里面注册。注意,这里的object path要跟com.ylmf.DBusGlib.conf里面的path一样哦。
service-main.c就是我们ylmfdbus的main。很简单,自身只带了一个注册函数。
Makefile。每当我们新写了一个object。要相应的想里面添加内容。后面会详细说明。
 
文件policykit-ylmf.c是验证函数的实现,在我们注册的GOboject类里面,我们只用在开头#include policykit-ylmf.h调用里面的check_auth()方法就ok了。
com.ylmf.PolicyKit.policy这个文件是公共的权限说明文件。在它里面定义了你每一个动作(action)的id,说明,消息,权限等等信息。本来可以都用一个。但是为了代码的易懂。希望每一个函数都对应一个动作(action)。
 
 
以上是我们ylmfdbus已经有的装备。现在,我们来看怎么要写一个自己的object并且注册进去。
普通的gobject一般只用头文件和c文件。我们要写一个能注册到ylmfdbus里面的gobject需要多一些的内容。多出两个文件。一个xml用来描述方法然后通过dbus-binding-tool转换成一个xxx-glue.h约定的头文件。一个list文件通过glib-genmarshal来生成一个xxx-marshal.c文件,用来连接gobject的信号类型的c文件。
接下来说说gobject文件里面多的东西。
一个完整最小的类(hello world)应该包含以下东西:
/* helloworld.h */
#ifndef __HELLO_WORLD_H_
#define __HELLO_WORLD_H_

#include <glib-object.h>


G_BEGIN_DECLS

#define HELLO_WORLD_TYPE                  (hello_world_get_type())
#define HELLO_WORLD(o)                    (G_TYPE_CHECK_INSTANCE_CAST((o),HELLO_WORLD_TYPE,HelloWorld))
#define HELLO_WORLD_CLASS(o)              (G_TYPE_CHECK_CLASS_CAST((o),HELLO_WORLD_TYPE,HelloWorldClass))
#define HELLO_WORLD_GET_CLASS(o)          (G_TYPE_INSTANCE_GET_CLASS ((o), HELLO_WORLD_TYPE, HelloWorldClass))
#define IS_HELLO_WORLD(o)                 (G_TYPE_CHECK_INSTANCE_TYPE ((o), HELLO_WORLD_TYPE))
#define IS_HELLO_WORLD_CLASS(o)           (G_TYPE_CHECK_CLASS_TYPE ((o), HELLO_WORLD_TYPE))


typedef struct _HelloWorld HelloWorld;
typedef struct _HelloWorldClass HelloWorldClass;

struct _HelloWorld
{
    GObject parent;

};

struct _HelloWorldClass
{
    GObjectClass parent_class;

};


GType
hello_world_get_type(void);


G_END_DECLS


#endif //__HELLO_WORLD_H_
 
 
/* helloworld.c */
#ifdef HAVE_CONFIG_H
#   include <config.h>
#endif

#include <glib.h>

#include "helloworld.h"


static void
hello_world_finalize(GObject        *object);

static GObject*
hello_world_constructor(GType                  type,
                guint                  n_construct_properties,
                GObjectConstructParam *construct_properties);



G_DEFINE_TYPE(HelloWorld,hello_world,G_TYPE_OBJECT)


static void
hello_world_class_init(HelloWorldClass *klass)
{
    GObjectClass *object_class= G_OBJECT_CLASS(klass);

    object_class->constructor = hello_world_constructor;
    object_class->finalize = hello_world_finalize;
}


static void
hello_world_init(HelloWorld *objname)
{
}


static GObject*
hello_world_constructor(GType                  type,
                guint                  n_construct_properties,
                GObjectConstructParam *construct_properties)
{
    GObject *object;
    HelloWorld *objname;
    object = G_OBJECT_CLASS(hello_world_parent_class)->constructor(
                                    type,
                                    n_construct_properties,
                                    construct_properties);
    objname = HELLO_WORLD(object);
    return object;
}


static void
hello_world_finalize(GObject        *object)
{
    HelloWorld *objname;
    objname = HELLO_WORLD(object);
    if(G_OBJECT_CLASS(hello_world_parent_class)->finalize){
       G_OBJECT_CLASS(hello_world_parent_class)->finalize(object);
    }
}


HelloWorld*
hello_world_new(void)
{
    return HELLO_WORLD(g_object_new(HELLO_WORLD_TYPE,NULL));
}
 
 
 
我们应该注意到我们的helloworld类的有一个统一的前缀"hello_world"这个很重要。在我们用dbus-binding-tool和glib-genmarshal工具来自动生成代码的时候加参数的时候要是一样的。其次是描述接口的xml文件中标签<method name="">这里填写的是方法名的后缀。开头用大写。然后自己要在类里面实现这个方法。这个是基本的一个GObject对象,我们可以在后面写其他的方法了。在写其他方法的时候,我们必需要注意每一个方法有两个参数是固定的。第一个参数是这个对象本身的指针,最后一个参数是DBusGMethodInvocation类型的指针,这两个参数必须有。如果有其他的输入输出函数则需要在描述接口的xml文件中进行描述,描述接口的xml文件中每一个方法必须添加 <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
 
例子:在xml里面写的是HelloWorld 然后前缀是ylmf_xxxx  生成的xxx-glue.h文件里面的方法名就是ylmf_xxxx_hello_world。
dbus-binding-tool --prefix= --mode=glib-server --output=xxx-glue.h xxx.xml
dbus-binding-tool --prefix=auto_login --mode-glib-server --output=auto-login-glue.h login.xml
 
然后在我们的类的c文件里面需要include xxxx-glue.h文件的时候。注意不要写到文件开头。要写到函数定义完实现具体函数前.
 
最后需要注意的地方 是我们在xxxx__class_init方法里面需要添加 dbus_g_object_type_install_info函数用来在注册的时候使得dbus认识这个object
 
如果每一个方法中有首先第一个传入参数必须是这个类本身,然后后面跟入需要传入的参数,并且在xml文件里面描述。如果需要返回,最好传入DBusGMethodInvocation这个类型。然后调用dbus_g_method_return来进行函数的调用
 
 
下载地址
http://u.115.com/file/clfvkwwz#Downloadhelloworlddbus.tar.gz
 
 

 

1.4.6 消息记录
    为了在程序运行的时候能诊断错误,GLib在系统控制台提供了几个有用的小工具。下面是他们按照优先级来描述:(PS:优先级从低到高)
    g_message() 表示程序在正常运行时候的行为提示。
 
    g_warning() 不会导致程序错误的行为信息(至少调用它的时候没有错误)(PS:程序员只关系error不关心warning)
 
    g_critical() 这个警告可能会导致错误。
 
    g_error() 对于程序来说最严重的错误,调用它会立刻终止程序。
 
    这些函数的参数和printf()的很像,都是一个格式化字符串和一个替代格式的参数列表。虽然你不必在每一个信息的最后都用换行符结束,但是为了确保你和你程序的用户知道这些信息是从你的程序发出的以区别其他程序,在编译的时候你就必须设置G_LOG_DOMAIN这个宏。用一个简单的字符串来定义你的应用软件或者是库。大部分的GNOME基本程序都是在编译选项的时候定义G_LOG_DOMAIN的。具体这样做: -DG_LOG_DOMAIN=\"名字\"。
    下面这个程序演示了这四个函数:
 
    /* messagedemo.c -- show logging features */

    #include <glib.h>

    #define NR 42

    int main(int argc, char **argv)
    {
            g_message("Coffee preparation engaged");
            g_warning("Bean canister #%d empty", NR);
            g_critical("Water flow failure");
            g_error("Heating element incinerated");

            /* this program shouldn't reach this point */
            return 0;
    }
 
    (PS:这个输出是我改过的)
gcc `pkg-config --libs --cflags glib-2.0` -DG_LOG_DOMAIN=\"yu\" message.c -o message
    这个程序的输出看来想这样:
    yu-Message: Coffee preparation engaged
 
    (process:3976): yu-WARNING **: Bean canister #42 empty
 
    (process:3976): yu-CRITICAL **: Water flow failure
 
    yu-ERROR **: Heating element incinerated
    aborting...
    Aborted
    (PS:说明修改生效了)
 
    为致命的错误标记错误等级
    g_error()函数调用会在之前的程序中产生一个错误的信息。告诉你这个程序被终止了(也可能是core dump[1])。你还可以配置致命错误在其他日志里面的优先级是他们表现的一样。例如:
    [code]
    g_log_set_always_fatal(G_LOG_LEVEL_WARNING|G_LOG_LEVEL_CRITICAL)
    [/code]
 
    这个函数的参数是一个掩码,用来设置这些警告和严重消息的行为。你可以用下列常量按位进行或运算:
     G_LOG_LEVEL_CRITICAL
 
    G_LOG_LEVEL_WARNING
 
    G_LOG_LEVEL_MESSAGE
 
    G_LOG_LEVEL_INFO
 
    G_LOG_LEVEL_DEBUG
 
注意:如果应用程序是用的GTK+或者是GNOME的库,你可以用命令行的选项 --g-fatal-warnings 来改变所有的警告等级。
 
    
    自由消息
 
    如果你有一条消息是不适合用预格式化的日志函数来输出,那你可以将它用g_print(), g_printerr()输出。g_print()输出到标准输出端(stdout),g_printerr()输出到标准错误端(stderr)。
    注意:不同于日志消息函数(g_message())。函数g_print()和g_printerr()需要自己去指定消息中换行符的位置。
    
    你可能会奇怪,为什么不用函数fprintf()来完成这个工作。不管你信不信(ps:反正我是信了),这个函数可能在Windows系统上无法正常工作。另外一个原因是英文,你可以定义自己的消息处理方式和可以改变的日志消息。并可以把他们输出到你想输出的任何地方(如一个窗口或者日志文件)。下面是一个例子:
 
    
    /* printhandlerdemo.c */

    #include <stdio.h>
    #include <glib.h>

    #define N 1

    /* print messages in ALL CAPS */
    void my_printerr_handler(gchar *string)
    {
        GString *msg;

        msg = g_string_new(string);
        msg = g_string_ascii_up(msg);
        fprintf(stderr, "%s\n", msg->str);
        g_string_free(msg, TRUE);
    }

    int main(int argc, char **argv)
    {
        /* print to stdout */
        g_print("If you lie %d time, no one believes you.\n", N);

        /* print to stderr */
        g_printerr("Ouch.\n");

        /* but if you lie all of the time... */
        g_set_printerr_handler((GPrintFunc)my_printerr_handler);
        g_printerr("%d. Ouch. Ouch. Ouch. (Hey, that really hurts.)", N);

        return 0;
    }
 
 
 
    你可以在后面1.5.1的小节里面看到函数my_printerr_handler()是怎么工作的。下面是这个程序的输出:
    If you lie 1 time, no one believes you.
    Ouch.
    1. OUCH. OUCH. OUCH. (HEY, THAT REALLY HURTS.)
 
    正如你看到的那样,你可以用g_set_print_handler() 和 g_set_printerr_handler()这两个函数来设置你的打印函数。他们唯一的参数是一个GPrintFunc函数。定义如下:
typedef void (*GPrintFunc) (const gchar *string);
    所以,你的回调函数必须定义为返回值是void而且仅有一个字符串参数的函数。
    当你要构建自己的错误消息要用到两个或者两个以上的函数时候:g_sterror()和g_strsignal()。strerror()和strsignal()都是独立于平台的实现。g_strerror()函数接受一个如EBADF或EINVAL错误代码,并将其转换到一个更易于理解的消息“错误的文件描述符”或“无效的参数。”同样,g_strsignal()返回的一个信号的名称时,给出一个数字信号代码。
    相对于strerror()和strsignal()来说它们的优势不是独立于不同平台,而是创造了一个UTF-8的输入输出库。例如:GTK+
 
 
 
 
 
 
 
 
 
 
    [1]什么是Core Dump?
    Core的意思是内存, Dump的意思是扔出来, 堆出来.开发和使用Unix程序时, 有时程序莫名其妙的down了, 却没有任何的提示(有时候会提示core dumped). 这时候可以查看一下有没有形如core.进程号的文件生成, 这个文件便是操作系统把程序down掉时的内存内容扔出来生成的, 它可以做为调试程序的参考.core dump又叫核心转储, 当程序运行过程中发生异常, 程序异常退出时, 由操作系统把程序当前的内存状况存储在一个core文件中, 叫core dump.
 
 
 

 

1.4.5 定时器
 
只要你系统上的时钟够准确,Gtimer的精度可以超过了秒表。
下面是一个例子:
 
/* gtimerdemo.c -- demonstration of GTimer */


#include <glib.h>
#define DURATION 200000

int main(int argc, char **argv)
{
        GTimer *clock = NULL;
        gint i;
        gdouble elapsed_time;
        gulong us;  /* microseconds */

        clock = g_timer_new();
        g_timer_start(clock);
        g_print("Timer started.\n");

        g_print("Loop started.. ");
        for (i = 0; i < DURATION; i++) { ; }
        /* wasting CPU time like this is only allowed in programming examples */

        g_print("and finished.\n");
        g_timer_stop(clock);
        g_print("Timer stopped.\n");

        elapsed_time = g_timer_elapsed(clock, &us);
        g_print("Elapsed: %g s\n", elapsed_time);
        g_print("         %ld us\n", us);

        g_timer_destroy(clock);

        return 0;
}
 
    你需要了解的Gtimer都在这个小例子里面。clock变量是一个Gtimer数据结构的指针,一开始我们设置为NULL。为了创建Gtimer这个结构,我们调用g_timer_new()函数,返回值就是我们要的clock指针。(一个Gtimer结构的新指针)。
    我们用函数g_timer_start()来启动我们刚刚生成的时钟。事实上这个程序在运行着一个循环,除此之外什么也没有做,就是浪费cpu的时间而已。[1]。之后,我们用函数g_timer_stop()来停止及时。
    当我要获得此刻计数器的计数结果的时候,调用:
   
   time = g_timer_elapsed(timer, us_ptr);
 
    它的返回值是经过时间的秒数,并且是一个双精度的浮点数。此外,如果你需要获得微妙级的小数部分,你可以传入一个gulong变量的指针作为us_ptr。因为这个数字没有包括完整的秒数树脂,所以第二部分的积分要乘以100万来进行类型转换。如果你想要一个微秒的总数的话。(ps:后面这句翻译的@#¥#@¥@!#¥)
 
    注意:
如果你不关心到微秒,us_ptr可以设置为NULL。
 
    最后,你可以用函数g_timer_reset(timer)重新设置定时器。不用定时器的时候,你需要销毁它g_timer_destroy(timer)。
 
[1] 如果在编译这个程序的时候没有打开编译优化,这个循环就会被执行。

其实之前两篇都是在csdn一个博客上搞过来的,感谢那兄弟。以后我来接着翻译把 。。争取一年可以翻译完。。我的英文很菜,翻译的不好,轻喷。。。。。

 

 

1.4.4 Unicode和字符编码
    C标准的字符串和上一节的字符串。在这些函数里面都不用担心长度的问题,因为每一个C标准的字符和gchar字符都是一个字节长。(PS:gchar只是char的一个define)
    在本节中的函数跟上一节的有所不同,因为他们都是针对Unicode的字符和字符串。Unicode是一个广泛统一的字符集,可以表示地球上任何语言中的任何字符。Unicode可以用作通用字符集。(UCS;see ISO 10646)Unicode原本只有16bit的编码。经过不断的扩张。我们GLib可以支持三种不同的编码方案。(详见 man utf-8)
    UCS-4是一个完整的32bit UCS兼容编码。(其实就是UTF-32,我就不翻译了直接贴wiki)
      UTF-32 (或 UCS-4)是一種將Unicode字符編碼的協定,對每一個Unicode碼位使用恰好32位元。其它的Unicode transformation formats則使用不定長度編碼。
    因為UTF-32對每個字符都使用4位元組,就空間而言,是非常沒有效率的。特別地,非基本多文種平面的字符在大部分文件中通常很罕見,以致於它們通常被認為不存在佔用空間大小的討論,使得UTF-32通常會是其它編碼的二到四倍。
    雖然每一個碼位使用固定長定的位元組看似方便,它並不如其它Unicode編碼使用得廣泛。與UTF-8及UTF-16相比,它有點更容易遭截斷。即使使用了"定寬"字型,除非在一些非常有限的清況下,否則它並不會使得計算顯示一個字串的寬度更加容易。主要原因是,存在著一個字符位置會有多於一種可能的碼點(結合字符)或一個碼點用多於一個字符位置(如CJK表意字符)。結合符號也意味著,文書編輯者不能不能將一個碼點視同一個編輯上的單位。
    
   (不过后面他注解了一些。我还是翻译吧)每一个UCS-4字符有4个字节长。用两个低位字节来表示Unicode(一般情况下高位的字节就为0).在GLib的数据类型UCS-4是gunichar。一个gunichar是标准的Unicode类型有32bit宽。在glib中某些函数用UCS-4字符串(gunichar *)。
    UTF-16是原生编码().每一个UTF-16字符有2个字节长。GLib使用gunichar2类型来表示UTF-16字符。跟UCS-4一样,你可以看到UTF-16的字符串是(gunichar2 *)。
    UTF-8是在实践中最重要的。因为UTF-8跟ASCII是兼容的。普通的ASCII有8bit长。但是其他的字符可能有2个字符长或者更长。所有文件里面ascii编码的格式都包含在utf-8的有效区间里面,但不能反过来。UTF-8因为在文件里面存储是不均匀的,如果随机读取就不可能得到一个完整的某一部分,每次都必须从头开始遍历字符,这是一个很明显的缺点。因为UTF-8没有一个同意的大小,GLib里面没有一个数据类型来特殊的表示UTF-8。但是,你可以用正常的gchar字符和字符串来表示(gchar *)UTF-8.
    GLib中普遍使用Unicode标准的32位UCS- 4 gunichar类型。
    以下函数针对单个的Unicode字符:
    gboolean g_unichar_validate(gunichar c)
    如果字符c是Unicode,返回真。
    gboolean g_unichar_isdefined(gunichar c) 
    如果字符c是Unicode标准分配的,返回真。
    gboolean g_unichar_isalnum(gunichar c)
    如果字符c是字母或者数字,返回真。
    gboolean g_unichar_islower(gunichar c)
    如果字符c是小写字母,返回真。
    gboolean g_unichar_isupper(gunichar c) 
    如果字符c是大写字母,返回真。
    gboolean g_unichar_istitle(gunichar c)
    如果字符c是标题字符(titlecase?)返回真。(ps:啥叫标题字符 贴devhelp解释来看,就不翻译了)
 
    Determines if a character is titlecase. Some characters in Unicode which are composites, such as the DZ digraph have three case variants instead of just two. The titlecase form is used at the beginning of a word where only the first letter is capitalized. The titlecase form of the DZ digraph is U+01F2 LATIN CAPITAL LETTTER D WITH SMALL LETTER Z.
 
    注意
    Titlecase doesn't appear much in English (or many other languages, for that matter). A titlecase letter is usually some sort of composite character or ligature where the first part of the composite goes to uppercase when the letter is capitalized at the start of a word. An example is the Lj in the Croatian word Ljubija.(估计用不上不翻译了)
    gboolean g_unichar_isalpha(gunichar c)
    如果字符c是一个字母,返回真。
    gboolean g_unichar_isdigit(gunichar c)
    如果字符c是一个十进制字符,返回真。
    gboolean g_unichar_isxdigit(gunichar c)
    如果字符c是一个十六进制字符,返回真。
    gboolean g_unichar_ispunct(gunichar c)
    如果字符c是某种符号或标点符号,返回真。
    gboolean g_unichar_isspace(gunichar c)
    如果字符c是一个空白的形式,包括空格,制表符和换行符。返回真。
    gboolean g_unichar_iswide(gunichar c)
    如果字符c通常需要两倍的一个正常的字符在屏幕上绘制的空间。返回真。
    gboolean g_unichar_iscntrl(gunichar C)
    如果字符c是一个Unicode控制字符。返回真。
    gboolean g_unichar_isgraph(gunichar C)
    如果你可以打印字符c,就是说如果它不是一个控制字符,格式字符,或空间。返回真。
    gboolean g_unichar_isprint(gunichar c) 
    这个函数跟g_unichar_isgraph()很像,除了空格是返回的真。
 
    如果你有一个gunichar的字符,而你想知道这个字符在Unicode是怎么分类的。你可以用:
    g_unichar_type(c)
    这个函数返回的一个常量(跟多信息在[TUC])
    G_UNICODE_LOWERCASE_LETTER:小写字母
 
    G_UNICODE_UPPERCASE_LETTER:大写字母
 
    G_UNICODE_TITLECASE_LETTER:标题字符信
 
    G_UNICODE_CONTROL:Unicode控制字符
 
    G_UNICODE_FORMAT:使用Unicode格式字符
 
    G_UNICODE_MODIFIER_LETTER:修饰符(看起来很奇怪的字母发音修改)
 
    G_UNICODE_SURROGATE:两个16位表示一个字符的Unicode字符的复合
 
    G_UNICODE_UNASSIGNED:目前未分配的字符
 
    G_UNICODE_PRIVATE_USE:保留给私人,内部使用的字符
 
    G_UNICODE_OTHER_LETTER:杂信
 
    G_UNICODE_COMBINING_MARK:马克可能与另一个字母组合
 
    G_UNICODE_ENCLOSING_MARK:马克,其中包含的另一封信
 
    G_UNICODE_NON_SPACING_MARK:马克,通常需要没有空间来打印;上的立场取决于另一个基本字符
 
    G_UNICODE_DECIMAL_NUMBER:数字
 
    G_UNICODE_DECIMAL_LETTER_NUMBER:一个字母的数字
 
    G_UNICODE_OTHER_NUMBER:任何其他的数字
 
    G_UNICODE_CONNECTION_PUNCTUATION:绑定的标点符号
 
    Dashlike G_UNICODE_DASH_PUNCTUATION:标点符号
 
    G_UNICODE_OPEN_PUNCTUATION:开幕标点符号(如一个左括号)
 
    G_UNICODE_CLOSE_PUNCTUATION:结束标点符号
 
    G_UNICODE_INITIAL_PUNCTUATION:标点符号
 
    G_UNICODE_FINAL_PUNCTUATION:终端标点符号
 
    G_UNICODE_OTHER_PUNCTUATION:任何其他标点符号
 
    G_UNICODE_CURRENCY_SYMBOL:货币货币符号
 
    G_UNICODE_MODIFIER_SYMBOL:修饰符号(例如,一个口音)
 
    G_UNICODE_MATH_SYMBOL:数学符号
 
    G_UNICODE_OTHER_SYMBOL:任何其他奇怪的符号
 
    G_UNICODE_LINE_SEPARATOR:换行符(例如,一个换行符)
 
    G_UNICODE_PARAGRAPH_SEPARATOR:划分段落
 
    G_UNICODE_SPACE_SEPARATOR:一个空的空间
 
    下面是转换成单个gunichar字符的函数:
    gunichar g_unichar_toupper(gunichar c)
    返回字符c的大写字母,如果c已经是大写了,就不改变。
    gunichar g_unichar_tolower(gunichar c)
    返回c的小写字母。
    gunichar g_unichar_totitle(gunichar c)
    返回c的titlecase。
    gint g_unichar_digit_value(gunichar c)
    如果这个c是数字返回gint的数字,如果不是数字则返回-1.
    gint g_unichar_xdigit_value(gunichar c)
    跟上一个一样,不过返回的是十六进制。
 
    现在你已经知道关于gunichar的很多有趣的事情,你可以会想知道你现在能对这种类型的能做什么事情了。大多数情况下,你必须冲UTF-8字符串中把Unicode字符给提取出来,在提取的过程没读取一个字符,你可能要对字符进行一定的有效性验证。
    注意:你看到的函数里面,你可能会有一个带NULL结尾的字符串,但是这并不是一直必要的。如果一个函数需要gssize这种类型的参数,可以指定在UTF -8字符串函数应该处理的字节数。如果你要告诉一个函数来处理整个NULL结尾的字符串的大小就使用-1。(ps:窘,翻译的乱七八糟)
 
 
(1.4.4太长了 翻译起来 太有挫败感了。一下午就到这里。。。我现弄着后面比较上的把。。。窘。。。。而且感觉用的机会也不多)
 
 
 
 
 
 
 
 
 
    

继续阅读

 

 

1.4 Basic Utilities

(基本函数,这个Utilities不知道如何译,就写成函数吧,因为后面确实在讲函数,嘿嘿……)

为了简化你的程序与C语言以及系统的交互,GLib提供了大量的函数。 要了解GLib的函数处理数据结构部分,请看1.5节。

 

1.4.1 内存管理

如果你使用GLib提供的内存管理例程,它可以避免你处理很多让你头痛的事。 GLib提供了的附加的错误检查与侦测。在下面的表格中为C程序员提供了一个参考。

你可以使用g_malloc(),g_realloc(),g_free()来代替malloc(),realloc(),free(),它们提供了相同的处理方式。为了将申请的内存在使用前清零,你可以使用g_malloc0()。 注意,它的语法看起来像malloc,而不是calloc()。

 

GLib Function

Corresponding C Function

gpointer g_malloc(gulong n_bytes)

void *malloc(size_t size) with error handling

gpointer g_malloc0(gulong n_bytes)

like malloc(), but initializes memory as in calloc()

gpointer g_try_malloc(gulong n_bytes)

like malloc() without error checking

gpointer g_realloc(gpointer mem, gulong n_bytes)

void *realloc(void *ptr, size_t size) with error checking

gpointer g_try_realloc(gpointer mem, gulong n_bytes)

realloc() without error checking

void g_free(gpointer mem)

void free(void *ptr)

 

 

 

注意: 如果你有一些特殊的原因要使用函数的返回值的话,你可以使用g_try_malloc()和g_try_realloc(),如果出错的时候,它们会返回NULL。 你可以在某些不是非常关键的地方使用(如某些用来提高性能的额外缓冲区),或者某些测试的时候。

自然,如果你想覆盖GLib的保护机制,你必须要清楚你在做什么。 对大多数程序来说,这些像g_malloc()的普通函数能节约你大量的代码、错误、以及时间。

一般情况下,你没有必要为malloc或g_malloc指定具体的要申请的块的大小。一般都是使用sizeof()来告诉编译器或运行时系统申请某种类型的某个倍数。 为了与数据类型相符,你必须要将malloc()返回值进行强制转换。 强制转换要用大量的括号和星号,所以GLib提供了一些宏,如g_new(),g_new0()以及g_renew()。 下面是一些示例代码。

 

 

 

typedef struct _footype footype;
footype *my_data;
/* Allocate space for three footype structures (long version) */
my_data = (footype *) g_malloc(sizeof(footype)*3);
/* The abbreviated version using g_new */
my_data = g_new(footype, 3);
/* To initialize the memory to 0, use g_new0 */
my_data = g_new0(footype, 3);
/* Expand this block of memory to four structures (long version) */
my_data = (footype *) g_realloc(my_data, sizeof(footype)*4);
/* Shorter version */
my_data = g_renew(my_data, 4);

 

在上面的代码段中你可以清楚地看出,g_new()是g_malloc()的简化,g_renew()是g_realloc()的简易形式,g_new0()是g_malloc0()的简易形式。

警告:记住你在使用g_new()时需要一个类型,就像使用sizeof()一样。 有时候像这样的的语句会产生编译错误:

b = g_new(a, 1) (a只是一个变量,而不是类型)

产生错误的原因就是因为g_new只是一个宏,应该给它传递类型,而不是某个类型的变量。

 

内存块

 

GUI程序一般倾向于重复申请大小相同的内存块(原子)。而且,那儿有一些相关的原子内存(atom)。GLib使用了一种称为“memory chunks”的方法为程序提供了相应的原子内存。一个内存块由一些原子内存组成的;所以块的大小肯定是原子内存大小的整数倍。

这儿有一个使用g_mem_chunk_new()来申请块的例子: 

 

GMemChunk my_chunk;
my_chunk = g_mem_chunk_new("My Chunk",         /* name */
                           42,                 /* atom size */
                           42*16,              /* block size */
                           G_ALLOC_AND_FREE);  /* access mode */

g_mem_chunk_new()有四个参数,第一个参数是内存块的名字,第二个参数是原子内存的大小(在这里是42),第三个参数是块的总共大小,最后一个参数是访问模式。这个函数的返回值是指向GMemChunk的指针。

注意: GMemChunk并不是一个数据结构。它是一个内存管理系统,它管理的内存片断里面含有数据结构。

第四个参数“访问模式”为你提供了如何创建和申请原子内存。一共有两种方式:

G_ALLOCC_AND_FREE允许内存池在任何时间返回单个的原子内存。

G_ALLOC_ONLY仅允许在处理整个内存块的时候申请原子内存。使用这个模式要比使用G_ALLOC_AND_FREE高效。

下面的例子指示了如何申请或释放原子内存。

 

gchar *data[50000];
gint i;
/* allocate 40,000 atoms */
for(i = 0; i < 40000; i++)
{
  data[i] = g_mem_chunk_alloc(my_chunk);
}
/* allocate 10,000 more atoms and initialize them */
for(i = 40000; i < 50000; i++)
{
  data[i] = g_mem_chunk_alloc0(my_chunk);
}
/* free one atom */
g_mem_chunk_free(my_chunk, data[42]);

 

在这里,g_mem_chunk_alloc()和g_mem_chunk_alloc0()都可以申请单独的原子内存。这两个函数像g_malloc()和g_malloc0()一样,返回一个指向原子内存的指针,但是,他们使用GMemChunk结构而不是大小作为参数。g_mem_chunk_free()函数使用指向单独原子内存的指针来做为参数,它将释放的内存返回到内存池中。

警告: 使用g_mem_chunk_free来释放原子内存的时候,该原子内存所在的内存块必须是使用G_ALLOC_AND_FREE模式创建的。除此之外,使用g_free()来释放原子内存的时候将会引起一个段错误。产生段错误的原因是因为内存块的释放函数将会导致两次调用free()。

 

有一些函数会一次性的在整个内存块上操作原子内存。下面的例子展示了这些函数的用法。

 

/* free up any unused atoms */
g_mem_chunk_clean(my_chunk);
/* free all unused atoms in all memory chunks */
g_blow_chunks();
/* deallocate all atoms in a chunk */
g_mem_chunk_reset(my_chunk);
/* deallocate a memory chunk */
g_mem_chunk_destroy(my_chunk);

 

g_mem_chunk_clean(chunk)会检查chunk并且释放那些不再使用的内存。这个例程给你提供了一些手工管理基本内存的方式。g_mem_chunk_free()函数并不必须立即释放原子内存。只有在方便或者必须的时候,GLib才会释放它。g_mem_chunk_clean()强迫它立即释放。

g_blow_chunks()会在程序中所有的outstanding内存块中运行g_mem_chunk_clean()。

g_mem_chunk_reset(chunk)会释放chunk中的所有原子内存,包括那些在使用的。你要小心使用这个函数,因为他可能会释放掉仍要使用的原子内存。

g_mem_chunk_destroy(chunk)会释放包括chunk本身以及chunk中的所有原子内存。

 

在内存管理上面,GLib为你提供了一些宏来减少你的输入。

 

typedef struct _footype footype;
GMemChunk *pile_of_mem;
footype *foo;
/* create a memory chunk with space for 128 footype atoms */
pile_of_mem = g_mem_chunk_new("A pile of memory",
                             sizeof(footype),
                             sizeof(footype)*128,
                             G_ALLOC_AND_FREE);
/* the same thing, with g_mem_chunk_create */
/* the name will be "footype mem chunks (128)" */
pile_of_mem = g_mem_chunk_create(footype, 128, G_ALLOC_AND_FREE);
/* allocate an atom */
foo = (footype *) g_mem_chunk_alloc(pile_of_mem);
/* the same thing, with g_mem_chunk_new */
foo = g_mem_chunk_new(footype, pile_of_mem);
/* the same thing, but zero out the memory */
foo = g_mem_chunk_new0(footype, pile_of_mem);

 

从上面这些代码中,可以很容易的明白这些宏的意图。注意,如果你知道原子内存的类型的话,g_mem_chunk_create()是一个比g_mem_chunk_new()更简单的方法。还有,每个宏都会自动地将块名字拼凑起来。 g_mem_chunk_new()和g_mem_chunk_new0()是与g_new()和g_new0()相对应的用来操作内存块的函数。

如果你想知道当前内存块的统计数字,使用g_mem_chunk_print(chunk)可以得到一个简单的报告。 使用g_mem_chunk_info()可以得到所有内存块的详细信息。

 

 

 

1.4.2 Quarks (夸克)

为了在程序中标识一块数据,你一般有两种方式可选:数字或字符串。但是这两者都有一些缺点。数字是非常难以辨认的。如果你开始粗略的知道需要多少标签,你就可以定义一个枚举类型和一些字符符号。但是,你没法在运行的时候动态添加标签。

另一方面,你可以在运行的时候动态的添加或修改字符串,而且它们是很容易理解的。 但是,字符串比较要比数字比较花更长的时间,而且在内存中管理字符串有一些你可能不愿意处理的额外麻烦。

 

GLib提供了GQuark类型,它整合了数字的简单和字符串的易用。在它内部,它就是一个易于比较和复制的整形数。GLib将这些数字映射为你的字符串,并且你可以在任何时间取得字符串所对应的值。

要创建一个quark,使用下面两个函数之一:

 

GQuark quark;
gchar *string;
quark = g_quark_from_string(string);
quark = g_quark_from_static_string("string");

 

这两个函数都是以字符串作为它们唯一的参数。它们的区别是g_quark_from_string()会在映射的时候创建一个字符串的拷贝,但是g_quark_from_static_string()并不会。

警告:小心使用g_quark_from_static_string()。在每次执行它的时候会节约很少的CPU和内存,但是,在你的程序中增加了附加的依赖性可能会导致你的程序有一些bug,所以,或许节约的那一点开销并不值得你去使用该函数。

 

如果你想检验某个字符串是否有一个quark值,调用:

g_quark_try_string(string)

这个函数的返回值是该字符串所对应的quark值。如果返回0的话,说明没有与这个字符串相对应的quark值。

从quark恢复到string使用:

string = g_quark_to_string(quark);

如果它执行成功,它将会返回quark对应的字符串的指针。但是你不能在这个指针上调用free(),因为这个字符串并不是一个拷贝。

下面是一个简短的示例代码:

 

GQuark *my_quark = 0;
my_quark = g_quark_from_string("Chevre");
if (!g_quark_try("Cottage Cheese"))
{
  g_print("There isn't any quark for /"Cottage Cheese/"/n");
}
g_print("my_quark is a representation of %s/n", g_quark_to_string(my_quark));

 

注意:GQuark值是分配给字符串的数字,并且很容易测试它们的等同性。然而,它们并没有数字顺序。你不能用quark值来进行字母顺序测试。因此,你不能用它们作为排序关键字。如果你想比较quark所代表的字符串,你必须先用g_quark_to_string()来提取相应的字符串,然后才可以使用strcmp()或g_ascii_strcasecmp()。

 

 

 

1.4.3 C字符串

GLib提供了一些字符串函数来与标准C库进行交互(不要对GString疑惑,在1.5.1节将会讲到)。你可以用这些字符串函数来扩充或代替stringf()、strdup()或strstr()等。

下面的这些函数会返回一个新的字符串缓冲区的指针,所以你在使用完后必须要释放它们。

gchar *g_strdup(const gchar *str)

复制str并返回它的一个拷贝。

gchar *g_strndup(const gchar *str, gsize n)

返回str中前n个字符的一个拷贝。这个拷贝总会在最后附加一个NULL结束符。

gchar *strnfill(gsize length, gchar *fill_char)

创建一个长度为length并被fill_char填充的字符串。

gchar *g_strdup_printf(const gchar *format, ...)

像sprintf()一样格式化字符串和参数。但是,你没必要像sprintf()一样创建和指定一个缓冲区,GLib将这些自动做了。

gchar *g_strdup_vprintf(const gchar *format, va_list args)

类似于上面的那个函数,它跟vsprintf()类似,使用C的可变参数能力,有关可变参数可以在stdarg(3)的手册中找到。

gchar *g_strescape(const gchar *source, const gchar *exception)

将一些特殊控制字符转换成相应的ASCII,如,将Tab转成/t,这些转换有:退格(/b)、换页(/f)、换行(/n)、回车(/r)、反斜线(/变成//),双引号(" 变成 /")。任何附加的非ASCII字符会转换成相应的8进制表示(例如escape会变成/27)。你可以在字符串的exceptions中指定任何特定的例外。

gchar *g_strcompress(const gchar *source)

与g_strescape()相反,它是将ASCII格式的字符串转为真正的控制字符。

gchar *g_strconcat(const gchar *string1, ..., NULL)

它接受任意数量的string作为参数,并返回它们的连接后的字符串。你必须将NULL作为这个函数的最后一个参数。

gchar *g_strjoin(const gchar *separator, ..., NULL)

连接一些字符串,并且添加分隔符在每个字符串之间。如gstrjoin("|", "foo", "bar", NULL)会产生"foo|bar"。像g_strconcat(),你也必须将NULL作为最后一个参数传给这个函数。如果将separtor参数设为NULL的话,g_strjoin()就会等同于g_strconcat()了。

 

在下面的函数中,你应该为返回的结果申请空间。GLib并不会返回一个拷贝给你。它们与C相对应的函数非常像,参数要包含一个足够大的缓冲区来进行字符串处理。

gchar *g_stpcpy(gchar *dest, const gchar *src)

拷贝src到dest,包括最后的NULL字符。如果它执行成功,会返回dest中结束符拷贝的指针。它在进行高效的字符串连接时是非常有用的。

gint g_snprintf(gchar *string, gulong n, const gchar *format, ...)

像snprintf()一样,你必须确保string有足够大的空间。而且你必须要用n来指定这个缓冲区的大小。返回值是输出字符串的长度,也有可能这个输出字符串为了适应缓冲区的大小而被截断。这是C99的标准,并不只是你机子上传统C库的行为。

gint g_vsnprintf(gchar *string, gulong n, const gchar *format, va_list list)

跟上个函数类似,不过是变长参数。

gchar *g_strreverse(gchar *string)

将string里面的字符顺序反转。返回值仍然是string。

gchar *g_strchug(gchar *string)

将string开头的空白字符都移除。将string中相应的字符进行左移,返回string。

gchar *g_strchomp(gchar *string)

将string结尾的空格都删掉,返回string

gchar *g_strstrip(gchar *string)

将string开头的结尾的空白字符都删掉,返回string。

gchar *g_strdelimit(gchar *string, const gchar *delimiters, gchar *new_delimiter)

将string中的delimiters替换为new_delimiter。如果delimiters是NULL的话,这个函数会使用" _-|<>. "; 这些是G_STR_DELIMITERS中的标准集。返回值是string。

gchar *g_strcanon(gchar *string, const gchar *valid_chars, gchar *substituter)

将string中的,不属于valid_chars中字符的那些字符都替换为substituer。返回string。注意,这个函数是g_strdelimit的一个补充。

 

 

在下面的函数中,除了g_ascii_dtostr()之外,都不改变它们的参数。

gchar *g_strstr_len(const gchar *haystack, gssize haystack_len, const gchar *needle)

在haystack中遍历haystack_len长度,如果找到了needle字串,则返回这个位置的指针,如果没有找到则返回NULL。

gchar *g_strrstr(const gchar *haystack, const gchar *needle)

类似于上个函数,这个函数将会从后面开始查找,但是它并没有haystack_len参数。

gchar *g_strrstr_len(gchar *haystack, gssize haystack_len, gchar *needle)

与g_strrstr()相同,但是它只在前haystack_len个字符中查找。

gsize g_printf_string_upper_bound(const gchar *format, va_list args)

检查format和args,返回格式化后所需要缓冲区的最大值。

gdouble g_ascii_strtod(const gchar *nptr, gchar **endptr)

将string转为双字长度的浮点数。如果你提供了一个有效的endptr指针地址,这个函数会将指针设置到string中被转换的最后一个字符的位置。与strtod()的区别是这个函数忽略了C locale。

gchar *g_ascii_dtostr(gchar *buffer, gint buf_len, gdouble d)

将d转换为ASCII字串。将转换后的忽略C locale然后写入到buffer中,最大长度为buf_len。目标字串的长度永远不会超过G_ASCII_DTOSTR_BUF_SIZE。这个函数返回buffer的指针。

注意:使用g_ascii_strtod()和g_ascii_dtostr()来读写文件或数据流,并不都是人可读的。 因为这些函数使用统一的标准,是区域无关的格式,为了解决某些特定的问题。 例如,一些人将本地设为German,然后运行你的程序,你无须担心本地化的数字之间逗号和句点的意义转换。

 

 

最后,这儿列出一些操作字符串数组的函数。 NULL指针来表示这些数组的结束。

gchar **g_strsplit(const gchar *string, const gchar *delimiter, gint max_tokens)

使用delimiter来将string切割成至多max_tokens个部分。返回值是新申请的一个字符串数组,用来保存被切割的这些部分。这个字符串数组必须由你自己释放。 如果输入字符串是空的,这个返回值也是一个空的数组。

gchar *g_str_joinv(const gchar *separator, gchar **str_array)

将字符串数组组合成单个字符串,并将这个新申请的字符串返回。如果separator不空,g_str_joinv()会在每个字符串之间添加上一个separator分隔符。

gchar **g_strdupv(gchar **str_array)

返回str_array的一个完整拷贝。

void **g_strfreev(gchar **str_array)

释放str_array数组以及这些字符串。

警告: 除了g_strfreev()之外,不要使用其它的函数来释放像g_strsplit()或g_strdupv()创建的字符数组。