Archive for the ‘C语言的那些事儿’ category

typedef那回事儿

30 1 月, 2011

typedef是一种特殊的声明方式,不过它与普通声明(详见这里)的含义取大不相同。普通声明的主角是“变量”,它或是创建一个新变量或是对外文件变量使用前的声明;而typedef声明的主角则是“类型”,通过这个声明对一种数据类型引入新的名字。从引入新名字这个角度来说,typedef声明又和宏定义有些相似:用新名字代替已有的名字。不过,在本文接下来的叙述中你会看到这两者之间的区别。

typedef是特殊的

正如本文一开始所说的那样,typedef是特殊的声明。我们最常见以及常用的方式如下:

/* 代码段1 */
struct stuinfo
{
	char id[20];
	char name[20];
	int age;
};
typedef struct stuinfo stu; /* 语句1 */

通过typedef声明为stuinfo结构体引入了一个新的名字stu。现在stuinfo结构和stu属于同一种数据类型,只不过两者在声明一个变量时使用的名字不同:

/* 代码段2 */
stu mystu1;
struct stuinfo mystu2; /* 语句2 */

通过上述两个代码段,可以再一次的理解typedef声明和普通声明的区别。代码段1通过typedef声明为stuinfo引入了一个新的名字stu;而代码段2则通过同一种数据类型的不同名称分别声明了两个同类型的变量。注意到语句1和语句2,除了语句1在声明前多了typedef关键字外,两者在形式上几乎一样,因此都可以通过上文所述的声明规则进行阅读。正是由于typedef这个关键字,使得这两种声明的含义有着巨大差异。

其实,像上面的举例那样通过typedef声明而省去一个struct并没有多大的意义。使用typedef声明的最大优点是可以简洁的表达一个指针。比如ANSI C中的signal(),它的定义如下:

void ( *signal(int signum, void (*handler)(int)) ) (int);

考验你的时刻到了!你是否能快速说出这个声明的含义?

这个复杂的语句声明了signal函数,这个函数有两个参数signum和handler;signum参数是一个整型变量。handler是一个函数指针,指向一个拥有整型参数并且返回空值的函数;signal函数的返回值是一个函数指针,该指针同样指向一个拥有整型参数并且返回空值的函数。

对于这个复杂声明的解读的确很令人费劲。但是经过typedef的改进,它的阅读过程就简化了很多:

 typedef void (*sighandler_t)(int);
 sighandler_t signal(int signum, sighandler_t handler);

通过typedef的声明,使得sighandler_t是这样一种类型:它是一个函数指针,该函数拥有一个整型参数并且返回空值。第二条语句则声明了函数signal,它拥有两个参数signum和handler。并且这个函数的返回值和参数handler都是sighandler_t类型的。

虽然这样的声明在形式上简洁许多,不过和普通声明一样,此时阅读声明时仍然要记住声明符号的优先级规则。这种困扰在C语言中是难以避免的。

typedef int x和#define x int是不一样的

typedef和宏定义看似都是文本替换,但其实质不同。typedef表面上是对已有数据类型引入新名称,实则是对数据类型的严格封装。这种封装体现在下述两个方面。

首先,经过宏定义后的类型名可以进行再次扩展,但是经过typedef引入的类型名则不能进行扩充。比如:

#define myint1 int
unsigned myint1 x; /* 正确 */

typedef int myint2;
unsigned myint2 x; /* 语法错误! */

由于typedef是一种严格的数据封装,它只引入了myint2类型而没有引入unsigned myint2类型。也就是说,通过typedef的声明,编译器只能识别myint2类型。而unsigned myint2既不是基本类型也不是经过typedef声明过的类型,编译器就无法识别。

其次,在连续的几个变量声明中,使用typedef定义的类型能够保证所有变量均为相同类型,而用宏定义的变量则无法保证统一性。比如:

#define myint1 int *
myint1 x,y; /* 经过宏替换后为: int *x,y; */

typedef int * myint2;
myint2 x,y;

由于宏定义只是直接的文本替换,因此只能保证x是整型的指针变量而y为整型变量。而typedef定义过的类型myint2则是对int *的完全封装,所以x和y均为整型的指针变量。

C语言中的名字空间

在说明名字空间之前,请先阅读下面的代码:

/*
 *Author: edsionte
 *Email:  edsionte@gmail.com
 *Time:   2011/02/03
 */

#include < stdio.h >
#include < string.h >

struct id
{
	int id;
}id;

typedef struct name
{
	char name[20];
}name;

struct name name1;
name name2;

int main()
{
	id.id = 1;
	strcpy(name1.name,"hello,");
	strcpy(name2.name,"edsionte!");
	printf("id.id = %d, %s%s\n",id.id,name1.name,name2.name);
	return 0;
}
/* 运行结果 */
edsionte@edsionte-laptop:~/code/expertC$ gcc tpdef.c -o tpdef
edsionte@edsionte-laptop:~/code/expertC$ ./tpdef
id.id = 1, hello,edsionte!

你可能已经发现在上述代码中出现了多个id和name,并且这样的代码可以成功的编译。这些相同的名字标签为何可以同时出现?每个标签代表什么含义?这些问题将是下面分析的重点。

以上述代码中10到13行的代码为例,这条语句中包含三个id标签。它们分别对应C语言中三种常见的名字空间:

  • 结构标签:这种标签用于结构体、联合体和枚举类型;struct后的id即为此类型的名字空间;
  • 成员名:每个结构体或联合体内部都与属于自己的名字空间;struct内部的成员id即为此类型;
  • 标签名:声明中的标示符;比如最后一个id,他是struct id类型的变量;

由于这三种标签所处的名字空间不同,因此它们可以同时存在。但是在同一个名字空间中不能出现多个同名的标签。常见的例子就是一个结构体内不可能出现同名的变量。

根据上面对名字空间的划分,15到18行的代码的解释为:struct后的name属于结构体标签;结构体内部的name属于成员名;而最后一个name属于声明的标示符;整条语句的含义是通过typedef声明将name结构体重命名为name。

通过上面对typedef的分析,你应该对于struct name和name均可以声明一个变量不再陌生。此处我们用名字空间的来理解他们的区别,20句中的name属于结构标签,21句中的name属于一种类型的名称。

上述同名的情况在日常的代码中实属罕见,这里只是为了说明名字空间而特别的举例。一般为了提高代码的可阅读性,最好对容易产生混淆的标签加上特别标记。比如VFS中inode和dentry结构体,两者内部均有flag一字段。尽管不同的结构体内有各自的名字空间,但是实际命名时仍然采用i_flag和d_flag。

有了上面的基础,本部分一开始所举例的代码也就可以轻松阅读了。

参考:

《C专家编程》 人民邮电出版社;(美)林登(LinDen.P.V.D) 著,徐波 译;

声明那回事儿

29 1 月, 2011

C语言中的变量声明是让程序员比较苦恼的一件事,因为过多的优先级规则使得阅读声明并不能像自然方式那样从左至右的阅读。比如下面这个声明:

int (*(*fun)())();

对于这个声明,你能准确说出它的含义吗?这个声明涉及到本文的两大主题:什么是声明和声明的阅读规则。本文的最后将给出这个声明的准确含义。

声明和定义

在C语言中,提到声明就不得不提到定义。这里说的声明既包含变量的声明又包含函数的声明。对于函数的定义和声明易于理解,比如:

int myfun2();

int myfun2()
{
	printf("I am myfun2~\n");
	return 0;
}

所谓函数的定义就是对该函数进行具体的功能实现,而函数声明则是对该函数返回值和参数类型的说明,使得其他函数感知到这个函数的存在,以便在需要时直接调用该函数。

变量的声明具体分为两种情况:定义型声明和外部引用型声明。

定义型声明其实就等价于定义。C语言的定义是指为变量分配内存空间,并在需要时为其赋一个初值。定义型声明用于创建一个新的变量,它在定义这个变量的同时也声明了这个变量。比如下述代码就声明(定义)了一个变量num,并将0作为其初值。

int num = 0;

使用外部引用型声明是由于要在当前程序中使用定义在其他文件中的变量。也就是说这个变量是已存在的,因此这种声明并不包含变量的定义。比如:

file1:
int a = 100;
file2:
extern int a;

C语言中的变量只能有一个定义,但是它却可以有多个extern的声明。因为extern声明并不分配内存空间,只是告诉引用这个变量的函数:这个变量已经定义过了,你可以直接的使用。

声明的组成

声明确定了变量的基本类型和相应的初值(如果需要的话)。一个完整的声明包括三部分:一个类型说明符,一个或多个声明器(declarator)和一个分号。

类型说明符用于描述所要声明变量的类型;分号说明了声明的结束;声明器是标示符以及和它组合在一起的指针符、函数括号和数组下标等,有时候也将初始化内容放在声明器中。多个声明器用逗号隔开。关于声明和声明器的关系可参考下图:

该图所示的语句声明了四个变量,其基本类型都是整型。由于四个变量分别对应着不同的声明器,则最终的变量类型就有所不同。整型变量a的声明器即为标示符a;第二个声明器为*b=NULL,它声明b是一个指针变量,其初值为NULL。由于类型说明符为int,则说明了b是一个指向整型的指针;第三个声明器为(*c)[20],它声明c是一个数组指针,该指针指向拥有20个元素的数组。由类型说明符得知该数组的元素都是整型的;最后一个声明器为*j[20],其说明j是一个指针数组,该数组有20个元素,每个元素都是指向整型变量的指针。

优先级规则

了解了声明的组成后,到了该给出声明优先级规则的时候了。C语言中声明的优先级规则如下:

1.声明从最左的标示符开始,然后按照下面的优先级规则依次读取;

2.具体的优先级为:

  • 2.1 被括号括起来的那部分;
  • 2.2 后缀部分;如果后缀为( ),表明这是一个函数;如果后缀为[ ],表明这是一个数组;
  • 2.3 前缀部分;*表示指向…的指针;

3.如果const或volatile关键字后紧邻基本类型说明符,则它作用于该类型的变量;否则,const和volatile作用于仅靠它左边的星号,即作用于指针变量;

上述规则需要参考实际的声明来慢慢理解。下面通过两个简单的声明举例来说明上述优先级规则:

const int (*p)();
const int *p();

声明1:

  • 首先找到标示符p,由于声明1中的p和*被包括起来,根据规则2.1得知p是一个指针。该指针指向什么类型是接下来读声明的关注对象;
  • 标示符的后缀部分是( ),根据规则2.2得知该指针指向一个函数。这个函数返回值是什么类型是接下来读声明的关注对象;
  • 读完上述声明符号后,剩下了const int,根据规则3得知这个函数的返回值是一个只读型的整数;

综合上面的几部分可得知该声明的含义:p是一个指针,它指向一个函数。这个函数没有参数,返回一个只读型的整数。

声明2:

  • 首先找到标示符p,它与*没有被包括在一起,因此可以排除p是一个指针;
  • 由于标示符的后缀部分是( ),根据规则2.2得知p是一个函数。该函数返回什么类型是接下来读声明的关注对象;
  • 由于p标示符前有*,则说明该函数的返回值是一个指针。至于该指针指向什么样的数据是接下来对声明的关注对象;
  • 通过队则3可得知该指针指向一个只读型的整型变量;

通过上述的分步分析可得知该声明的含义:p是一个函数,它没有参数,返回值是一个指针。该指针指向一个只读型的整型变量。

通过上述的优先级规则,可以轻松的阅读任何一个声明。从上述两个举例中也可总结出阅读声明的大致方法,首先判别该声明是声明一个函数还是一个变量;再根据具体的声明类型切换接下来读声明的关注对象;最后未读的基本类型就是最后一次关注对象的类型。上述的大致方法可详细总结如下:

  • 1.找到最左边的标示符,表示已被阅读;
  • 2.若已阅读符号右方紧邻[,则从左至右阅读到与之配对的 ] 为止。这一段符号表示 当前的关注对象是一个数组,该数组的元素类型未知;接下来的关注对象是数组的元素的类型,转4;否则转至3;
  • 3.若已阅读符号右方紧邻(,则从左至右阅读到与之配对的 )为止。这一段符号表示当前的关注对象是一个函数,该函数的返回值类型未知;接下来的关注对象是函数的返回值类型;顺序执行4;
  • 4.如果已阅读符号左边的符号是(,则寻找与之配对的 ),这一段符号表示已经阅读过;转至2;否则转至5;
  • 5.如果已阅读符号左边的符号是const,volatile和*之一,则不同符号代表不同的含义。const表示只读;volatile表示禁止编译器优化;*表示一个指针,该指针指向那种类型是接下来的关注对象,转4;否则转至6;
  • 6.剩下的未阅读的符号为基本的数据类型,即为当前关注对象的数据类型;

通过上述方法即可阅读一个声明,并理解它具体的含义。现在来解决本文一开始的那个声明:

int (*(*fun)())();

其步骤如下:

  • 找到标示符fun,fun已被阅读;
  • 步骤2,3,4不满足,转到步骤5;fun的左方为*,说明fun是一个指针。当前的关注对象是fun指向什么类型的数据,转到第4步;
  • *fun的左方为(,则(*fun)表示已被阅读的符号。当前的关注对象仍然是fun指向什么类型的数据,转到第2步;
  • (*fun)的右方紧邻(,则(*fun)()为已阅读的符号。它表示fun指向一个函数,该函数的返回值类型成为接下来的关注对象,转到第4步;
  • 步骤4不满足,转到步骤5;(*fun)()的左方为*,说明返回值类型为一个指针,该指针指向什么类型是接下来的关注对象。当前已读过的符号表示fun是一个指针,它指向一个函数,该函数返回一个指针,该指针指向什么类型未知;转至4;
  • *(*fun)()左方为(,则(*(*fun)())表示已被阅读。转至第2步;
  • (*(*fun)())的右方为(,则(*(*fun)())()为已阅读的符号,它表示当前的关注对象是一个函数。目前为止已阅读的符号表示fun是一个指针,它指向一个函数,该函数返回一个指针,该指针指向一个函数,该函数返回值类型未知;转至4;
  • 步骤4,5不满足转至6;int表示当前的关注对象为整型,目前所有的符号已阅读完毕。所有的声明符号表示fun是一个指针,它指向一个函数,该函数返回一个指针,该指针指向一个函数,该函数返回一个整型变量;

大功告成!

上述的分析过程看起来可能有些复杂和呆板,当你熟悉了整个分析过程后,这个过程就变得轻而易举。

举例

结合上面的理论分析,可以阅读下面的代码。这个代码一方面可以帮助你巩固上面的阅读规则,另一方面可以帮助你理解函数声明和指向函数指针的声明两者之间的区别。

/*
 * Author: edsionte
 * Email:  edsionte@gmail.com
 * Time:   2011/02/01
 */
#include 

int (*myfun1())();
int myfun2();

int (*myfun1())()
{
	//myfun1 return a pointer which point a function;
	//The return function hasn't argument and reurun a int value;

	int (*fun2)();
	printf("I am myfun1~\n");
	fun2 = myfun2;
	return fun2;
}

int myfun2()
{
	printf("I am myfun2~\n");
	return 0;
}

int main()
{
	int (*(*fun1)())();
	int (*fun2)();

	fun1 = myfun1;
	fun2 = fun1();
	fun2();

	return 0;
}

参考:

《C专家编程》 人民邮电出版社;(美)林登(LinDen.P.V.D) 著,徐波 译;

指向一维数组的指针

30 10 月, 2010

今天在看到typedef int (*int_arry)[10];这条语句时,因为对这样的定义使用较少,就想着编写一个test.c来试试看。不过,当我编写完一个简单的测试程序时,却发现我对指向一维数组的指针的使用了解甚少。

起初,我的程序是这样:

#include  < stdio.h >
typedef int (*int_array)[10];
int main()
{

	int a[10]={1,2,3,4,5};
	int_array i=&a;

	printf("%d=%d\n",i[4],a[4]);
      return 0;
}

编译后,提示如下错误:

test.c: In function ‘main’:
test.c:10: warning: format ‘%d’ expects type ‘int’, but argument 2 has type ‘int *’

也就是说,i[4]是一个int*型的指针。为什么会出现这样的错误?既然i是一个指向有10个整形元素数组的指针。那么将i指向数组a,然后使用i[4]获取第四个元素有什么错?

那我们从另一个角度来分析,一般i[4]这样的形式都可以看成*(i+4)这样的形式。i+4是什么?对了!i是一个数组指针,那么i+4也就是一个数组指针。如果将i所指的数组看作一个二维表的第1行,那么i+4就是指向第5行的指针。也就是说它相对于i所指向位置的偏移量为4*sizeof(int)*10个字节。因此*(i+4)仍然是一个指针,只不过它指向第5行的首个元素。

看来我们找到问题所在,i[4]并不是一个整形元素,而是一个指向整形元素的指针。上面程序中,我原本的意思是通过i来打印数组a中第四个元素。那么此刻我们应该这么修改:

printf("%d=%d\n",i[0][4],a[4]);

或者下面任意一句:

printf("%d=%d\n",(*(i+0))[4],a[4]);
printf("%d=%d\n",*(i[0]+4),a[4]);
printf("%d=%d\n",*(*(i+0)+4),a[4]);

你会发现,如果p是一个指向指针的指针,那么总能够通过两次的*、两次的[]或者一次*和一次[]取得这个指针最终指向的数据,因为说到底[]总能够化成*的形式。理解了这些,上面的语句对你也就不成问题了。got it?

C语言中的指针和数组

19 10 月, 2010

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

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的话——“指针神马的一切都是浮云~”。

const和enum

14 10 月, 2010

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。

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