问题:
今天碰到一个问题,变量的值被莫名改变,最开始也觉得很奇怪,后面发现其实是变量类型不匹配导致的。先上代码
#include <stdio.h>
#include <stdlib.h>
#define TYPE_PORT 0
#define TYPE_NUM 1
#define TYPE_MAX_LINE 2
typedef struct _cfg
{
unsigned short port;
unsigned short num;
unsigned short max_line;
}cfg_t;
int get_cfg(int type,int *result)
{
if(type == TYPE_PORT)
*result = 43432;
else if(type == TYPE_NUM)
*result = 2;
else if(type == TYPE_MAX_LINE)
*result = 444;
}
int main()
{
cfg_t cfg;
get_cfg(TYPE_PORT,(int*)&cfg.port);
get_cfg(TYPE_MAX_LINE,(int*)&cfg.max_line);
get_cfg(TYPE_NUM,(int*)&cfg.num);
printf("cfg.port:%d,cfg.num:%d,cfg.max_line:%d\n",cfg.port,cfg.num,cfg.max_line);
return 0;
}
上面这是一段很简单的代码,讲道理最后打印的内容应该是port =43432,num = 2,max_line =444。但实际测试你会发现max_line的值为0。
原因:
结构体里面的变量都是unsigned short型,每个变量占两个字节。而main函数在使用的时候,将变量强制转换为int型(4个字节)。main函数里面先读取结构体里面的第一个变量,然后再读第三个最后再读第二个变量。当读取第二个变量时,由于已经将cfg.num强制转换为int型,所以获取到的值会占据cfg.num后的四个字节。但是num在结构体里面实际上只占据了两个字节,所以多出的两个自己跑到max_line内存去了。由于我的机器是小端模式,所以被转为int型的num的高2字节部分跑到max_line去了,而高两字节都是0,所以max_line内存都被num清零了。而如果机器是大端模式,那么max_line的值就会变成num的值。
这里推荐另一种写法:
#include <stdio.h>
#include <stdlib.h>
#define TYPE_PORT 0
#define TYPE_NUM 1
#define TYPE_MAX_LIEN 2
typedef struct _cfg
{
unsigned short port;
unsigned short num;
unsigned short max_line;
}cfg_t;
int get_cfg(int type,int *result)
{
if(type == TYPE_PORT)
*result = 43432;
else if(type == TYPE_NUM)
*result = 2;
else if(type == TYPE_MAX_LINE)
*result = 444;
}
int main()
{
cfg_t cfg;
int tmp = 0;
get_cfg(TYPE_PORT,&tmp);
cfg.port = tmp;
get_cfg(TYPE_MAX_LINE,&tmp);
cfg.max_line = tmp;
get_cfg(TYPE_NUM,&tmp);
cfg.num = tmp;
printf("cfg.port:%d,cfg.num:%d,cfg.max_line:%d\n",cfg.port,cfg.num,cfg.max_line);
return 0;
}
这样就能保证不会出问题。这里采用的是用中间变量来保存结果,实际上采用中间变量的写法可以避免另外一种隐藏得很深的bug的出现。请继续往下看
问题:
请先看一段代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
int main(void){
struct in_addr addr1;
struct in_addr addr2;
char * str1, * str2;
addr1.s_addr = htonl(0x1020304); //点分形式为: 1.2.3.4
addr2.s_addr = htonl(0xc0a80101);//点分形式为:192.168.1.1
str1 = inet_ntoa(addr1);
str2 = inet_ntoa(addr2);
printf("%#lx -> %s \n", (long)addr1.s_addr, str1);
printf("%#lx -> %s \n", (long)addr2.s_addr, str2);
return 0;
}
inet_ntoa是将in_addr ip地址转为字符串的函数,返回一个字符串指针。in_addr是个结构体,里面只有一个变量,存储了一个32位的IPv4地址。讲道理,最终输出应该是:
0x4030201 -> 1.2.3.4
0x101a8c0 -> 192.168.1.1
但是实际上输出的结果是:
0x4030201 -> 192.168.1.1
0x101a8c0 -> 192.168.1.1
为什么最终两个结果会是一样的呢?
原因:
inet_ntoa这个函数,内部实现是使用静态内存的方式(参考:https://blog.csdn.net/ckt1120/article/details/7267095?locationNum=8&fps=1),所以每次调用这个函数,返回的指挥都是同一个。如果你前面仅仅使用一个指针来接收返回的指针,那么相当于这些指针都是同一个,在上面的代码也就是str1和str2其实是指向同一块内存,所以第二次的结果会覆盖第一次的结果。此时采用中间变量的写法如下:
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include<string.h>
int main(void){
struct in_addr addr1;
struct in_addr addr2;
char str1[200] = {0};
char str2[200] = {0};
addr1.s_addr = htonl(0x1020304);
addr2.s_addr = htonl(0xc0a80101);
memcpy(str1,inet_ntoa(addr1),strlen(inet_ntoa(addr1)));
memcpy(str2,inet_ntoa(addr2),strlen(inet_ntoa(addr2)));
printf("str1:%s,str2:%s\n",str1,str2);
return 0;
}
如上,调用一次inet_ntoa函数,立马将结果进行拷贝,这样就不会存在问题。我有一个同事还碰到过C++的string的c_str也有类似的问题,继续往下看:
问题:
看代码:
#include <string>
#include <iostream>
using namespace std;
int main()
{
string str = "this is str!";
const char *p = str.c_str();
cout << p << endl;
str = "hello world!";
cout << p << endl;
return 0;
}
大家看上面这段代码,第一次cout的内容是this is str!这应该没有异议,但是第二次cout的结果就要出人意料了,竟然是hello world! 很奇怪吧,指针p我们又没动过,第二次的值为什么会被改变呢?
原因:
我没找到c_str的内部实现,有知道的朋友麻烦告知下。但是可以明确的是,用完c_str(),如果再对string进行赋值,那么c_str()返回的指针就不能再使用,如果再使用,你会发现值和你预想的不一致,可能会导致程序出现莫名其妙的问题。
这仅仅是猜测的原因: c_str()返回的是string类的一个静态存储地址,因为是静态,所以所有的对象共同享用同一块地址。每次对string类进行赋值时,不管是深拷贝还是浅拷贝,这块静态内存都会被改变。猜测这个和inet_ntoa类似。
推荐写法:
这时候利用中间变量的方式又会很轻松避免这个问题,因为利用中间变量的话你就必须再执行一次拷贝操作,这样就能避免出错。
#include <string>
#include <iostream>
using namespace std;
int main()
{
string str = "this is str!";
char strtmp[1024] = {0};
strncpy(strtmp,s.c_str,sizeof(strtmp)-1);
const char *p = strtmp;
cout << p << endl;
str = "hello world!";
cout << p << endl;
return 0;
}
综上:本篇博客第二和第三个问题比较类似,第一个问题。。额,看看就好。总之,尽量避免直接对目的地址进行赋值的操作,而要通过中间局部变量进行另一次的赋值。这样能避免出现很多问题。
问题比较简单,权当做个笔记吧,欢迎评论探讨,分享你们遇到的坑!