变量类型不匹配引发的问题(变量值被莫名改变)

 

问题:

今天碰到一个问题,变量的值被莫名改变,最开始也觉得很奇怪,后面发现其实是变量类型不匹配导致的。先上代码

#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;
}
 

 

综上:本篇博客第二和第三个问题比较类似,第一个问题。。额,看看就好。总之,尽量避免直接对目的地址进行赋值的操作,而要通过中间局部变量进行另一次的赋值。这样能避免出现很多问题。

问题比较简单,权当做个笔记吧,欢迎评论探讨,分享你们遇到的坑!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值