C语言中的指针和数组

2010年10月19日 由 edsionte 没有评论 »

下面的内容节选自由我所执笔的会议记录。对于本文的不足之处,各位可以提出自己的看法。

Q1:指针和数组到底是怎么一回事?

A:指针和数组有本质的不同。指针就是一个内存地址,在32位系统下,一个指针永远占4个字节;数组是一块连续的内存空间,我们从一个已定义的数组中可以获得数组大小以及这块连续内存空间的起始地址。这个起始地址即数组首元素的地址,更具体的说是数组中首个元素的首地址。

在C语言中,只有一维数组。但是,当一个一维数组的元素是仍然是一维数组时,就形成了所谓的一维数组。如何印证这一点?二维数组在内存中的存储方式仍然是按照一维数组那样线性存储的,只不过这个数组的元素按照另一个一维数组线性存储。多维数组照此类推。

Q2:我还能获得关于数组和指针更详细的说明吗?

A:当然可以。

我们通常会这样使用一个指针:

int a[5];
int *p=a;

我们首先定义了一个指向int类型的指针p,在定义p的同时将其初始化为a;通常我们将这种行为称为“p指向一个大小为5的整形数组a”。事实上,我们可以更具体的想一下:p真的指向整个数组吗?当然不是。p只是指向一个int型变量而已。在这个例子中,p指向的是a数组的首元素。换句话说,p存储的是&a[0]。另外,p是变量,我们可以对p进行自加自减(在a数组有效的范围内),让p指向a数组的其他元素。换句话说,p可以存储整个数组中任意一个元素的地址(更具体的是这个元素的首地址)。而数组名a虽然也是个地址,但是它是一个常量,它永远存储的是数组的首地址,不可改变。

对于上述所言的p指针,我们可以通过指针方式*(p+i)和下标方式p[i]访问数组。对于数组名a而言,我们也可以通过*(a+i)和下标方式a[i]来访问数组。对于p指针而言,以指针方式访问数组的过程是这样的:我们通过p指针首先获取这个数组的首地址,然后加上i个偏移量,得到数组中第i个元素的地址,最后通过引用,得到具体的值。而对于p[i]这种下标访问方式,最终还是会被转化成上述指针访问方式。对于数组名a而言,与p方式相同。

另外还要注意,上述所说的i个偏移量,并不是加上i个字节那么简单;而是i个元素的总字节数。因此,给指针p加上一个整数和给指针p的二进制表示形式加上同样的整数,两者的含义是截然不同的。
另外,刚说到a是一个常量的问题,我想到了const关键字。上次在现代软件工程课程上,我听到有的同学说const所修饰的是常量。const修饰的当然是一个变量,只不过这个变量是只读的,即便这个变量的特性和常量相似。

Q3:对于数组a[3][4],我该如何理解更多的信息?

A:我们首先可以获知以下信息:数组a是一个包含有3个元素的数组,每个数组元素是一个大小为4的一维数组。因此,我们通常说这个数组有三行四列(但是你要清楚:内存中并不会出现3*4的那样的表格)。具体来说,二维数组a有以下三个元素:a[0],a[1],a[2];每一个元素又都是一个一维数组。对于一维数组a[0]来说,它又有4个元素:a[0][0],a[0][1],a[0][2],a[0][3];这4个元素是int型的。

以上就是关于这个数组的基本结构,下面从指针的角度来分析这个数组。

对于上述的数组名a,我们称为指向指针的指针(a是一个常量)。对于一维数组我们知道,数组名是数组中首元素的地址。对于上述所言的数组a,a当然也是这个数组首元素的地址。那么,数组a的首元素是什么?对了,就是a[0]。那么a这个指针存储的就是a[0]的地址,即a等价于&a[0]。那么a+i也就很明显了:a[i]的地址。再来想a[i]是什么?它是数组a中第i个元素。在数组a中,第i个元素是什么?它是一个大小为4的一维数组。对应到上述所言的“三行四列”,a+i即是指向第i行的指针。可以看到,i这个偏移量此刻是以行为单位的。

我们上述已说明,a+i指向a[i],a[i]是一个一维数组。a+i是一个指向指针的指针,那么现在取出a+i的值,也就是*(a+i)。从我们所说的“指向指针的指针”来看,*(a+i)也是一个指针,但是这个指针指向什么?它指向数组a[i]的首元素的地址,也就是a[i][0]的地址,即&a[i][0]。那么数组a[i]中第j个元素的地址是什么?那就是*(a+i)+j,即&a[i][j]。当然,也可以这么写:a[i]+j。

另外要说明的是,上述的a+i和*(a+i)的内存地址其实是相同,但是含义是完全不同的。具体原因,你可以从上面的陈述中得到答案。

Q4:难道指针就只有这么多内容吗?

A:当然不是。关于指针更高级的使用,还有指向一维数组的指针;指针数组;函数指针。可能它们的定义方式会让你迷惑,但是,无论如何,它们本质上仍然是指针。先了解最基本的指针,然后再理解这些高级指针就简单了许多。

上述Q1~Q3从某种角度来说,叙述的过于罗嗦,甚至有时候必须得咬文嚼字;并且,必须在实践的基础上理解上述内容。我个人的建议是,指针部分必须建立在“理论——实践——理论”这样反复的过程中,否则——套用现在最fashion的话——“指针神马的一切都是浮云~”。

grep和正则表达式

2010年10月16日 由 edsionte 4 条评论 »

这两天抽空看了一下正则表达式,随之也基本会用grep命令了。关于grep和正则表达式的具体使用,总结成文章如下。

什么是grep?

grep (Global search Regular Expression and Print out the line,全面搜索正则表达式并把行打印出来)是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。

什么是正则表达式?

正则表达式(Regular Express)就是由一系列特殊字符组成的字符串, 其中每个特殊字符都被称为元字符, 这些元字符并不表示为它们字面上的含义,而会被解释为一些特定的含义。

正则表达式的元字符

1.句点(.)

在正则表达式中,句点(.)可以匹配任何的单个字符。比如:

e.sionte:可以是edsionte,e#sionte,e7sionte等等;

ed…te:可以是edsionte,ed@wdte等

Try:

:%s/  a.d  /@/g

打开任意一个文本文件(本文中的所有命令都是使用这里的文章进行的测试),在vim命令状态下输入上述命令。这条命令的意思是:在所有行(%s代表所有行)中查找符合“  a.d  “的字符串(注意a前面和d后面均有一个空格),并且将符合条件的字符串替换成@;g代表将每一行中符合上述条件的所有字符串都做相应替换,否则只会替换每一行的第一个否和条件的字符串。在上述命令中,属于正则表达式的字符串为两个/之间的部分。

2.^符号

在正则表达式中,^符号表示跟行首进行匹配。比如:

edsionte is a good boy. edsionte likes eating.edsionte likes music.

^edsionte只会跟行首的edsionte进行匹配,而不是和位于行中的其他edsionte进行匹配。

Try:

:%s/^/      /

上述命令在一个文本文件的每行都插入五个空格。对于上述命令,在命令末尾加g和不加g作用是一样的。

3.美元符号($)

与^符号相反,美元符号($)代表跟行尾进行匹配。比如:

edsionte is eating a apple.edsionte will eat that pear.This cup of milk was drunk by edsionte.

edsionte$不会与末尾的edsionte进行匹配,因为这一行最后一个字符是句点(.)。而且它更不会与此行中其他两个edsionte进行匹配。同样的,对于上述命令,在命令末尾加g和不加g作用是一样的。

那么如何匹配行尾的句点?你会想到是这样:.$。不过,这个正则表达式表示的确实匹配行尾的任意的单字符。学过C语言的你,应该知道为什么了吧?我们上面说过正则表达式是由元字符组成,因此句点在正则表达式中不会代表它本身的意思。想要使用句点本身,即得在句点前加\。也就是说,\.$才会真正的匹配行尾的句点。这其实就是转义字符,got it?

Try:

:%s/..$//

删除每行的最后两个字符。

4.[…]结构

在正则表达式中,使用[…]来匹配包含在此结构中的一个字符。比如:

[Tt]he:不管是The还是the都否和匹配要求,这条正则表达式很好的处理了出现在行首的那些单词。但必须注意的是,在字符串“Ttheapple”中,使用上述正则表达式所匹配的是the,而不是Tthe。

[0123456789]:匹配的是任意一个数字,更简单的写法是[0-9]。

[^0-9]:匹配非数字的单字符。请注意这条正则表达式和^[0-9]的差异。

一些特殊的规则是,如果要匹配一个连字符,必须将连字符放在靠近左[的位置。如果方括号内有^必须将^放在所有匹配字符的前方,比如:

[-0-9]:匹配一个数字或者连字符。

[^-0-9]:匹配一个不是连字符或数字的字符。

Try:

:%s/[Hh]arry Potter[0-9]/HARRY POTTER/g

将诸如harry potter3、Harry potter5等这样的字符串都替换成HARRY POTTER。

:%s/HARRY POTTER/*harry potter*/g

将上一步替换后的字符串都换成*harry potter*。可能你会有这样的疑问:*,.,和^等这样的字符不是元字符吗,为什么这里可以直接使用*的本来意思?这是因为元字符只有在搜索串(也就是在正则表达式中)中才会有特殊意义,在替换串中则不会有特殊意义。

5.星号(*)

在正则表达式中,用星号来匹配零到多个紧靠在星号左边的那个字符。比如:

.*:表示零到多个任意字符。因为正则表达式总是寻求最大匹配,因此.*总是匹配一整行。

go*le:可以用来匹配gle,gole,goole,gooooooooooole等。

goo*le:最小的匹配为gole。

t.*t:匹配前后均为t的字符串,而且匹配的是最大长度的字符串。

Try:

:%s/  */ /g

将每一行中的一个到多个空格都全部换成一个空格。

:%s/[a-zA-Z][a-zA-Z]*//g

这个会有什么结果,试试就知道。

6.\{…\}结构

使用\{…\}这样的结构可以匹配精确数目的字符。比如:

t\{1,5\}:匹配1到5个连续的t。

[a-zA-Z]\{4,8\}:匹配4到8个连续的字母。

[0-9]\{8\}:匹配正好8个数字。

[a-z]\{4,\}:匹配至少4个连续的小写字母。

Try:

:%s/[a-z]\{5,\}/@/g

将至少5个连续的小写字母替换成@。

grep的使用

grep命令的基本使用方法是:grep 模式 文件名。文件中符合模式的字符串所在行均会被打印出来。比如:

grep include hello.c:即在hello.c中查找包含字符串include的那些行。

如何将上述我们所熟悉的正规表达式与grep命令结合起来?模式处即为正则表达式,并用单引号包含。比如:

grep ‘[Hh]arry [Pp]otter’ harry.txt:在harry.txt中查找harry potter所在的行,其中H和P是不区分大小写的。

另外,grep命令还有下面一些选项,可以使grep命令使用的更灵活。

-i:模式匹配时忽略大小写的差异;
-l:只输出匹配模式的文件名,而不打印匹配的行;
-q:匹配成功并不将结果在标准输出中显示;
-v:显示不符合模式的行;
-n:在匹配行前显示行号;

const和enum

2010年10月14日 由 edsionte 没有评论 »

const

我们可以将const关键字所修饰的变量看作是只读变量,也就是说这个变量不可被修改,只能读取其值。但它决不是常量。在标准C语言中,下面的语句是错误的:

	const int i=5;
	int a[i]={1,2,3,4,5};

诸如5,“hello”这样的常量以及宏定义过的常量之所以不能被修改是因为在内存中没有为他们分配空间,它们存储在运算单元的寄存器中。而上述所言的const修改过的变量说到底还是一个变量,内存中有存储那个变量的地方。只不过,这个变量有些特殊:只能读取。现在再看上面的代码,由于i为变量,它不能用来初始化数组的大小。

接着看下面的代码:

typedef char * charp;
char string[4] = "abc";
const char *p1 = string;
const charp p2 = string;
p1++;
p2++;

最后一句会报错,为什么?

在第三句中,p1是可变的,而*p是const的;第四句中,p2是const的,因此不能进行++;也许你此时对p和*p哪一个为const很迷惑。但是没有关系,这里有一个小技巧:将上述语句中的数据类型去掉,离const最近的就是只读的。比如const char *p1 = string去掉char后,*p离const最近,那么*p是const的;再如const charp p2 = string去掉数据类型charp后,p2离const最近,那么p2就是不可改变的。

不过,对于类似const (char *)p1 = string;这样的语句,上述办法有点阻碍。不过你可以将(char*)看成一个整体(你也可以认为是用typedef定义了一个新类型),那么就可以继续用上面的方法。

enum

关于中断处理函数的返回值类型,有下面的定义:

//linux/include/linux/interrupt.h
 98typedef irqreturn_t (*irq_handler_t)(int, void *);
//linux/include/linux/irqreturn.h
 10enum irqreturn {
 11        IRQ_NONE,
 12        IRQ_HANDLED,
 13        IRQ_WAKE_THREAD,
 14};
 15
 16typedef enum irqreturn irqreturn_t;

有一些书上使用的是int型,并且使用int也可以通过。这是为什么?

其实,了解了枚举类型的本质,这个问题就一目了然了。枚举类型的定义形式如下:

enum
{ 枚举常量1,枚举常量2,……,枚举常量n};

而枚举常量是用标示符表示的整形常量。并且C语言规定枚举常量的默认值依次为:0,1,……,n-1。对应到上面的定义代码,enum irqreturn中三个常量的值即为0,1,2。

内核中的动态定时器

2010年10月13日 由 edsionte 没有评论 »

内核中时间的相关概念

节拍率:系统定时器以某种频率触发时钟中断,这个频率就称为节拍率(tick rate)。节拍率是通过静态预处理定义的,被定义为HZ。对于x86体系结构来说,它的HZ为100。

节拍:两次时钟中断的间隔就称为节拍(tick)。可以看到,节拍等于节拍率分之一。

jiffies:全局变量jiffies用来记录系统自启动以来产生的节拍总数。通过jiffies/HZ就可获取系统自启动以来的秒数。

内核定时器

内核定时器,也称为动态定时器,是管理内核时间的基础,它是一种用来推迟执行程序的工具。前面中断的文章中我们说到内核将在稍候的时间执行下部分工作,具体是何时来执行就需要内核定时器。我们在使用关于定时器的相关函数时,先来看看内核是如何描述定时器的:

  10struct tvec_base;
  11
  12struct timer_list {
  13        /*
  14         * All fields that change during normal runtime grouped to the
  15         * same cacheline
  16         */
  17        struct list_head entry;//定时器链表入口
  18        unsigned long expires;//以jiffies为单位的定时值
  19        struct tvec_base *base;//
  20
  21        void (*function)(unsigned long);//定时器处理函数
  22        unsigned long data;//传给定时器处理函数的参数,无符号长整型
  23
  24        int slack;
  25
  26#ifdef CONFIG_TIMER_STATS
  27        void *start_site;
  28        char start_comm[16];
  29        int start_pid;
  30#endif
  31#ifdef CONFIG_LOCKDEP
  32        struct lockdep_map lockdep_map;
  33#endif
  34};

内核定时器使用起来也是相当简单的,基本的使用思路是:定义一个timer_list;初始化定时器,即对timer_list结构中的相关字段进行赋值;定义你自己的定时器处理函数;激活定时器;修改定时器,如果有必要的话;删除定时器。上述过程通过以下的API就可简单的完成。

秒字符驱动与内核定时器API

下面要分析的这个程序实现了一个秒字符设备驱动,程序整体的框架与之前我们分析的字符设备驱动相同。内核模块加载函数和卸载函数的功能和之前的字符设备驱动完全相同,因此接下来主要分析这个设备的操作函数。我们使用下面的结构来描述我们当前所讨论的秒字符设备。

struct second_dev
{
	struct cdev cdev;
	atomic_t counter;
	struct timer_list s_timer;
};

cdev即为内核中描述字符设备的结构体;counter用来记录秒数,它在测试程序中用来显示当前程序运行的时间。同时也是此设备驱动进行读写的数据;s_timer是为此设备所定义的定时器。每次定时一个HZ,也就是1秒,当定时到后,通过定时器处理函数对其进行修改,每次增加一个HZ。

接下来,我们一边分析驱动程序中的设备文件操作函数,一边学习内核定时器的API。

1.打开函数

秒字符设备在文件打开函数中实现对定时器的初始化以及赋值。

static int second_open(struct inode*inode,struct file*filp)
{
	//initializing the timer
	init_timer(&second_devp->s_timer);
	second_devp->s_timer.function=&second_timer_handler;
	second_devp->s_timer.expires=jiffies+HZ;
	add_timer(&second_devp->s_timer);//add a timer
	atomic_set(&second_devp->counter,0);//clear the counter
	return 0;
}

可以看到,内核定时器函数通过:init_timer(&t);来进行定时器的初始化,其中t为timer_list类型的指针。expires是用来设置定时器的定时值,单位是节拍数。我们可以看到初始定时值为一秒,即在当前时钟节拍数上加上一个节拍率。function则是上面提到的定时器处理函数。

另外,在open函数中,还通过add_timer(&t);来进行定时器的激活,也就是把当前这个定时器加入到内核定时器链表当中。由于我们在该设备对应的街头体中将counter设置为atomic_t(关于atomic.h头文件,本博客稍候会专门来分析,请持续关注~ e91 ),因此我们使用atomic_set函数对其进行初始化,其值设置为0。

2.读函数

static ssize_t second_read(struct file *filp,char __user *buf,size_t count,loff_t *ppos)
{
	int counter;

	counter=atomic_read(&second_devp->counter);
	if(put_user(counter,(int*)buf))
	{
		return -EFAULT;
	}
	else
	{
		return sizeof(unsigned int);
	}

}

这个读函数的意思是从内核中通过atomic_read函数读取counter,再通过put_user函数将其值返回至用户空间。

3.定时器处理函数

static void second_timer_handler(unsigned long arg)
{
	mod_timer(&second_devp->s_timer,jiffies+HZ);
	atomic_inc(&second_devp->counter);
	printk("Current jiffies is %ld\n",jiffies);
}

执行此处理函数时,说明先前定时器的时间已经到期。我们通过mod_timer(&t,jiffies+newdelay);来改变定时器的超时时间。这里我们每次增加一秒的时间。接着通过atomic_inc函数对counter加一,并且在内核中显示当前的节拍数。

4.文件释放函数

static int second_release(struct inode*inode,struct file*filp)
{
	del_timer(&second_devp->s_timer);
	return 0;
}

在文件释放函数中,通过del_timer(&t);就可以删除这个定时器

测试

1.make编译并将此模块插入内核:sudo insmod secondcdev.ko
2.查看/proc/devices文件中是否有主设备号为250的设备
3.创建该设备文件节点,使用命令:sudo mknod /dev/secondcdev  c  250  0
4.修改该设备文件权限:sudo chmod 777 /dev/secondcdev
5.编译测试文件:gcc -o test test.c
6.运行测试程序:./test;

关于测试程序代码如下:

#include < stdio.h >
#include < sys/stat.h >
#include < sys/types.h >
#include < fcntl.h >
#include < stdlib.h >
int main()
{
	int fd;
	int counter=0;
	int old_counter=0;

	//open the /dev/secondcdev device file
	if((fd=open("/dev/secondcdev",O_RDONLY))<0)
	{
		printf("Device open failed\n");
		return -1;
	}

	while(1)
	{
		read(fd,&counter,sizeof(unsigned int));//read the current second number
		if(counter!=old_counter)
		{
			printf("seconds after open /dev/second:%d\n",counter);
			old_counter=counter;
		}
	}

	return 0;

在测试程序中,首先创建一个设备文件;然后循环读取counter的值,如果前后两次读取的counter值不一样,那么就输出;由于我们在定时器处理函数中是每隔一秒修改一次定时器,因此测试程序将每个一秒输出一次counter值。可以看到,我们所编写的秒字符设备驱动使得数据(即counter)在内核空间和用户空间之间进行交互。

使用vim+ctags+cscope阅读内核源码

2010年10月11日 由 edsionte 13 条评论 »

有网络的时候,使用LXR(The Linux Cross Referencer)在线阅读源码固然是一件很爽的事情。在无网络的情况下,只通过vim进入内核源码那样查找就显得有些勉强。接下来,本文将介绍一种快速定位源码的工具:ctags和cscope。通过vim编辑器,再配合使用上述两种工具就可以快速定位想要查看的源码。好了,开始吧!

1.下载最新内核源码包,解压至主目录下

接下来的步骤当然可以在当前系统内核源码目录下进行,不过为了保险期间还是最好在主目录下专门创建一个存放源码的目录。下文所说的源码目录均指主目录下的源码目录。

2.安装ctags

使用命令:sudo apt-get install ctags

3.安装cscope

使用命令:sudo apt-get install cscope

4.成标签文件

在源码目录下通过命令make tags生成标签文件

5.生成索引文件

在源码目录下通过命令make cscope生成索引文件。

6.将索引文件导入vim中

使用命令打开vim的配置文件:sudo gedit /etc/vim/vimrc;然后在该文件下添加如下代码:

if filereadable("cscope.out")
    cs add cscope.out
endif

这样每次打开vim就可以直接使用cscope了。

完成以上步骤以后就可以通过vim进行源码的快速定位了。上述安装的ctags和cscope都属于源码索引工具,因此仅安装两者之一也是可以的。不错,虽然ctags可以快速定位所输入的标签,但cscope的使用方法则更灵活。所以两者同时安装使用起来更方便。下面是一些简单的使用方法,仅供参考。

1.使用ctags

ctags的使用特别简单,首先进入源码目录下,打开vim编辑器,在命令模式下输入:tag tag_name 按回车即可。通常tag_name可以是结构体名称,函数名称,宏变量名称等。通常输入上述命令后,因为找到的标签并不唯一,所以还需要配合使用下述命令:

tfirst:跳至第一个

tnext:跳至下一个

tlast:跳至最后一个

2.使用cscope

虽然ctags使用方便快捷,但有时候使用cscope更灵活,首先可以看cscope的帮助文件:

cscope 命令:
add  :添加一个新的数据库             (用法: add file|dir [pre-path] [flags])
find :查询一个模式                   (用法: find c|d|e|f|g|i|s|t name)
c:找到调用这个函数的函数
d:找到被这个函数调用的函数
e:找到这个 egrep 模式
f:找到此文件夹
g:找到这个定义
i:找文件 #包括这个文件
s:找到这个 C 符号
t:找到对其的赋值
help :显示此信息                     (用法: help)
kill :结束一个连接                   (用法: kill #)
reset:重置所有连接                   (用法: reset)
show :显示连接                       (用法: show)

通过上面的帮助文件可以发现如果想找到request_irq函数的定义处代码,即可使用这个命令:cs find g request_irq来进行查找。大多数情况下查找的结果并不唯一,因为需要在多个结果中通过头文件来继续查看。

通常是先通过cscope大致定位到头文件,再通过ctags在该头文件中详细定位。其实这两种工具并不局限于上述用法,更多用法可以再深入使用的过程中慢慢摸索,而且配合正则表达式等效果会更好。

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key