vimer linux kernel 爱好者

c语言中的强制转换类型

2014-11-19

这是看到的文章

原文地址 c语言中,任何变量都有一个地址,这个地址是由0和1二进制代码组成的,在声明变量 时,例如int,系统就会临时开辟一个存储空间,空间的大小根据类型的不同 而不同,最直接的表现就是分配的字节数不同。这个地址是硬件访问的依据,我们在源 代码定义的变量只不过是为了方便我们自己而定义的。 例如,定义如下:

int a;
float b;
double c;
long double d;

假设它们所占的字节分别是4、8、8、10,而且连续存储一段空间地址,起始地址是100,则我们可以得到如下分布的东东:

|_a_| 100
|___|
|___|
|___| 103
|_b_| 104
|___|
|___|
|___|
|___|
|___|
|___|
|___| 111
|_c_| 112
|___|

为了节约空间我们不画出c,当然上面的阿拉伯数字就是地址了,不过在实际的内存中 ,是一连串的0和1,但思想是差不多的。所有这些类型都是编译器告知的,由于前面我 们已经定义了int型,则编译器知道从a的开始地址往后取4个字节再把它解释成int型( 等等,这里的意思是虽然编译器知道这个变量是多少字节,但机器访问的时候并不知道 ?,就感觉矛盾,前面既然只取了4个字节,为何还要再来一次呢)。那么(float)a,就 是先按照int类型取出该值,再将该值按照int to float的规则转换成float类型,一句 话总结就是,强制类型转换就是先按照原先的类型取出该值,然后按照** to **的规 则进行强制转换,如果是(类型名)常数这种形式,则是将该常数to类型的方式强制转换。

还有,变量名与内存空间的关联关系不是由硬件指定的,而是由编译器为我们实现的。

不能简单地通过检查一个值的位来判断它的类型

指针也是一个变量,它自己占据一个四字节的地址空间(因为程序的寻址空间是2^32, 即4GB,所以指针的4字节大小完全够用),它的值是另一个东西的地址,这个东西可以 是普通变量、结构体、还可以是函数等,由于指针的大小是4字节,所以,我们可以将 指针强制转换成int类型或其他类型,同样,我们也可以将常数转换成int型再赋值给指 针。所有指针的空间的大小都是4字节它们只是声明的类型不同。

指针to指针的强制转换是将指针所指的内容的类型由原来的类型转换为后面的类型。

int a = 1;

int *p = &a;

float *p1 = (float *)p;

则p和p1的值都是&a,但是p是将&a地址中的值按照int型变量进行解释,而p1则是将& a地址中的值按照float型变量进行解释。

ANSI C规定,void指针可以复制给其他任意类型的指针,其他任意类型的指针也可以 复制给void指针,他们之间复制不需要强制类型转换。当然任何地址也可以复制给void型指针。

自己原来在指针这块这么差啊,受不鸟自己了。

姑且这么理解,自己在网上看到一个比较的例子:

int a = 8;
int *q;//指针(q)的类型是int *,指针指向的类型是int
q = &a;//给q赋值,但这里两者可以合写 *q = &a,仔细想想概念细节;
/*即使int *q = &a,我们最好将事实上也就是把q的类型看成int *,这样才会与&a的事
  实联系起来*/
printf("%d\n",*q);
/*结果就是8*/
printf("%d\n",q);
/*结果就是a变量的地址,同&a的结果是一样的*/

再看下面补充的:

float  f = 12.3;
float *fptr = &f;
int *p;

假如我们想让指针p指向浮点数f,那么就是p = &f吗,错,因为两边的类型不同,不能 直接赋值,需要强制转换。

p = (int *)&f;

如果有一个指针p,我们需要把它的类型和它所指向的类型改为TYPE *和TYPE,那么语法格式就是(TYPE *)p.

那么我们可不可以把一个整数当作指针的值直接赋给指针呢?就像下面的语句:

	unsigned int a;
	TYPE *ptr;//TYPE可以是int,char或结构体等类型
	...
	...
	a = 12345678;
	ptr = 12345678;//我们的目的是要使指针指向地址12345678(十进制)
	ptr = a;
	/*不用说,后面的两条语句是错误的,但我们可以使用下面的语句*/
	a = 12345678;//必须是合法的地址
	ptr = (TYPE *)a;
	/*这样就可以实现我们的目的,这里(TYPE *)a的意思是把无符号整数a的值当作一
个地址来对待,还有,我们可不可以反过来,即把指针指向的地址即指针的值当作一个
整数取出来,完全可以,下面的例子演示了把一个指针的值当作一个整数取出来,然后
再把这个整数当作一个地址赋给一个地址。*/
	int a = 123,b;
	int *ptr = &a;
	char *str;
	b = (int)ptr;//把指针ptr的值当作一个整数取出来
	str = (char *)b;//把这个整数的值当作一个地址赋给指针str

指针类型强制转换

int m;
int *pm = &m;
char *cp = (char *)&m;

pm指向一个整型,cp指向整型的第一个字节。

结构体之间的强制转换

struct str1 a;
struct str2 b;
a = (struct) b; //This is wrong
a = *((struct str1*)&b); //This is corrent

这一部分的缺陷继续去阅读经典的书籍去填补

利用宏来求结构体成员的偏移量

输入:

一个结构体定义type,这个结构体中某个成员变量的名字member以及它的地址ptr

输出:

包含此成员变量的结构体的地址

struct father_t {

	int a;
	char *b;
	double c;
}f;
char *ptr = &(f.b);
/*而不是 ptr = f.b; 这里ptr是结构体f的成员b的地址,而不是它指向的地址。
根据C语言对struct类型的存储特性,我们可以画这么一个图示:
*/

}
0  +------------+    <----我们要求的是这个地址
   |a (4 bytes) |
4  +------------+    <----我们知道这个地址
   |b (4 bytes) |
8  +------------+
   |c (8 bytes) |
16 +------------+

通过以上分析我们不难得出,我们只需要把当前知道的成员变量的地址ptr,减去它在结构体当中的相对偏移量4就得到了结构体的地址(ptr-4)。在linux内核中,有一个很好的宏叫做container_of,

#define offset(TYPE,MEMBER)((size_t) & ((TYPE *)0)->member)

这个宏的功能就是获得一个结构体成员在此结构体的偏移量

问题是,你能清楚的讲解上面语句的含义吗?我用了一天的时间去恶补基础,结果发现自己太菜了。

1.((TYPE *)0) 将零强制转换为TYPE类型的指针;

2.((TYPE *)0)->MEMBER 访问结构中的数据成员;

3.&(((TYPE *)0)->MEMBER) 取出数据成员的地址,即想对于0的偏移量,求的就是它。

4.(size_t)(&((TYPE *)0)->MEMBER) 结果强制转换,size_t应该最终为 unsigned int 类型。

#include<stdio.h>
#define offset(TYPE,MEMBER)((int)(&((TYPE *)0)->MEMBER))
struct _test_{
	char x;
	int y;
        float z;
};
int main()
{
	int temp = -1;
	temp = offset(struct _test_,z);
	printf("temp = %d\n",temp);
	return 0;
}

结果是 8.


下一篇 创业

Comments

Content