以下代码运行于64位Linux系统中
先看一下代码样本
HOSTENT *H = gethostbyname("www.csdn.net");
char **p = H->h_addr_list;
SOCKADDR_IN info;
while(*p) {
memcpy(&info.sin_addr, *p, 4);
cout << inet_ntoa(info.sin_addr) << endl;
++p;
}
- 第一行创建了一个结构体指针
H
,指向函数gethostbyname
,并传入了一个参数(即域名) - 第二行创建了一个指向指针的指针
p
,用来储存指针H
中存放的所有IP地址。 - 第三行创建了一个结构体
info
- 第四行是一个while语句,用来判断p中第一个元素是否为空指针,否就继续执行
- 第五行是将
p
中第一个元素从内存层面复制到结构体info
成员sin_addr
的地址中 - 第六行是将
info.sin_addr
中存放的IP地址二进制值转为一个char指针
并被打印屏幕上。
思考
这段代码当初是我刚学网络编程时抄的一位程序员的,但是由于目前我的技术水平有了质的飞跃,于是发现了一些问题。
也不能说是问题吧,毕竟这段代码完全没有问题(除非你把它原封不动的抄到Windows上执行,因为还需要初始化WSADATA)。
可以看到第三行声明了一个结构体变量info(struct sockaddr_in),因为在上述代码中我们是要用它来暂时性的储存IP地址的,所以才创建了它。
但其实,完全没必要声明(创建)这个变量,接下来看我说为什么。
大家都知道指针是指向一个内存地址的,也就意味着你可以用指针查看你权限范围内的内存中所有数据。
这是什么意思呢?
char c1 = 'A'; // 声明一个字符型变量c1并赋值为字母A
char c2 = 'B'; // 声明一个字符型变量c2并赋值为字母B
char *p = nullptr; // 声明一个指针p,暂时将它赋值为空
size_t c1_addr = (size_t)&c1; // 将c1的内存地址数字存入变量c1_addr
size_t c2_addr = (size_t)&c2; // 将c2的内存地址数字存入变量c2_addr
if(c1_addr < c2_addr) // 判断哪个地址值最小,将p指向最小的地址值的变量
p = &c1;
else
p = &c2;
cout << *(p + 1) << endl; // 打印为A
你无论如何怎么执行,它永远只会输出A,为什么呢?
变量c1的地址为0x0000002
变量c2的地址为0x0000001
c1被赋值为了A
c2被赋值为了B
指针p永远指向最小的那个内存地址,此处就指向了c2的内存地址,我们将p指向的地址加了1字节,也就变成了0x0000002,而这段地址中储存的值为A,这就是为什么这段代码永远只输出A的原因。
说这些是为了证明什么呢?
为了证明所有数据你都可以通过指针操控,也就意味着我们一开始的那段代码,其实根本不需要多声明一个变量info。
因为在第二行char **p = H->h_addr_list;
执行之后,此域名下所有的IP地址已经存入内存了。
而我们又知道,一字节为8比特,可以存放0~255之间所有的数字。
那IP地址是怎么组成的呢?
也就是4个0~255之间的数字组合在一起。
也就意味着他们在内存中的表现其实已经是一个完整的地址了,不需要再通过inet_ntoa
等函数进行转换了。
为什么呢?
一开始那段代码,只执行前两行的话,p的第一个元素指向的地址中的数据是3c cd ac 02
,而哪个域名的IP地址是60.205.172.2
,有没有发现什么奇妙的地方?
没错,IP地址已经出来了,只不过它是以十六进制的形式出现的。
如果你还没明白我想表达的意思,你可以执行一下下面这段代码。
unsigned char ip[4] = {
0x3c, 0xcd, 0xac, 0x02};
printf("%d.%d.%d.%d\n", *ip, *(ip+1), *(ip+2), *(ip+3));
结语
C语言是一个非常灵活非常不可思议的语言,只要你掌握好了它,那么你就可以在计算机中做任何你想做的事情。