在一次项目中,偶然使用了sscanf,这里就不详细说明它的用法,开门见山,说一下本文的意图,也希望其他同族兄弟了解。
上程序代码:
struct xxx{
unsigned char a[6];
char b[16];
};
int main(int argc, char** argv)
{
struct xxx xyz;
memset(&xyz, 0, sizeof(struct xxx));
strcpy(xyz.b, argv[1]);
sscanf(argv[2], "%x:%x:%x:%x:%x:%x", &(xyz.a[0]), &(xyz.a[1]), &(xyz.a[2]), &(xyz.a[3]), &(xyz.a[4]), &(xyz.a[5]));
printf("%s\n", xyz.b);
return 0;
}
执行:./test enp2s0 12:23:34:45:45:56
打印结果:xyz.b输出为空
gcc -g test.c -o test
上GDB调试结果:
说明一下,第一步,初始化xyz变量内存为0;
第二步,通过strcpy,将xyz.b赋值,图中显示为16进制;
第三部,执行sscanf后,xyz.a的数值诸葛元素赋值,但是xyz.b的前三个字节被置0了。
最终导致xyz.b输出为空。
原因说明:一定要摒弃老祖宗流传下来的玄学,开发过程中遇到的问题一定是有原因的。这里的问题显然是在执行sscanf之后出现了,那就只能深入看一下sscanf的使用。
sscanf 类型说明符:
类型 | 合格的输入 | 参数的类型 |
c | 单个字符:读取下一个字符。如果指定了一个不为 1 的宽度 width,函数会读取 width 个字符,并通过参数传递,把它们存储在数组中连续位置。在末尾不会追加空字符。 | char * |
d | 十进制整数:数字前面的 + 或 - 号是可选的。 | int * |
e,E,f,g,G | 浮点数:包含了一个小数点、一个可选的前置符号 + 或 -、一个可选的后置字符 e 或 E,以及一个十进制数字。两个有效的实例 -732.103 和 7.12e4 | float * |
o | 八进制整数。 | int * |
s | 字符串。这将读取连续字符,直到遇到一个空格字符(空格字符可以是空白、换行和制表符)。 | char * |
u | 无符号的十进制整数。 | unsigned int * |
x,X | 十六进制整数。 | int * |
sscanf(argv[2], "%x:%x:%x:%x:%x:%x", &(xyz.a[0]), &(xyz.a[1]), &(xyz.a[2]), &(xyz.a[3]), &(xyz.a[4]), &(xyz.a[5]));
程序中使用了%x,对应输入参数类型应该是int*,说明参数的原类型应该是4bytes,而这里xyz.a 数组中每个元素是一个字节,当做内存拷贝时,xyz.a[5] 实际被复制的按16进制表示是0x00000067。x86_64平台的内存存储是小端字节序(高字节在高位,低字节在低位),而这里GDB展示的是从低字节到高字节,从左往右,所以这里 xyz的头三个字节被3个0x00覆盖了。
因此只能将程序中的字符串mac地址转化成 int ,scanf输入参数的原型就不支持unsigned char。其实,单从这个程序而言,可以调换xyz.a 和 xyz.b的赋值顺序,也可以实现,下面来看一下:
但是切记,实际项目中慎用这种方式转化mac地址,要不然你一定会付出代价!!