记录C/C++库从32位机器向64为机器移植过程中导致的问题和解决方案

最近程序库从32为向64为机器移植后,在使用过程中出现了一些问题,其中包括上一篇中记录的由于va_list类型不一致导致程序core dump或者出现异常行为的原因,这篇文章记录一下库移植之后出现的小问题。持续更新…

问题一: unsigned long int类型长度不兼容并对其按位操作

一般情况下在32位和64位机器单纯使用该类型在不出现数字大小溢出的情况下一般不会出现问题。但是有一种情况需要特别注意,即当对该unsigned long int进行位操作时,此时的操作结果在32和64位机器的操作结果会不同而导致其他问题。

例如使用unsigned long int类型作为key对其按位进行加密或哈希时,这时的加密或哈希结果会出现问题。

解决方法

将unsigned long int类型换为uint32_t在64位机器上重新编译

问题二: 32位和64位默认对齐不同导致数据类型大小不同

在linux系统下,32位机器数据默认以4字节对齐,64为机器默认以8字节对齐。

看如下结构构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
struct Test{
int a;
char* data[0];
};
int main() {
std::cout << sizeof(Test) << std::endl;
Test t;
std::cout << "&t:" << &t << std::endl
<< "&t.a" << &t.a << std::endl
<< "&t.data:" << &t.data << std::endl;
return 0;
}

32位机器运行结果:

1
2
3
4
8
&t:0x7ffd49f978b0
&t.a0x7ffd49f978b0
&t.data:0x7ffd49f978b4

64位机器运行结果:

1
2
3
4
8
&t:0x7ffd49f978b0
&t.a0x7ffd49f978b0
&t.data:0x7ffd49f978b8

解决方案:

在声明结构体前面加上#pragma pack(1)或#pragma pack(4),强制改变结构体的对齐方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#pragma pack(4)
struct Test{
int a;
char* data[0];
};
int main() {
std::cout << sizeof(Test) << std::endl;
Test t;
std::cout << "&t:" << &t << std::endl
<< "&t.a" << &t.a << std::endl
<< "&t.data:" << &t.data << std::endl;
return 0;
}

Comment and share

C中va_list在32位和64位机器的区别与差异

在将程序从32位机器移植到64位机器的过程中经常出现一些奇奇怪怪的错误,这里记录一下在使用可变参数的过程中导致在32位机器上正常运行的程序移植到64位机器上之后出现段错误的发现过程以及解决方案。

首先看下面一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
void parse(va_list ap) {
char* arg;
arg = va_arg(ap, char*);
std::cout << arg << std::endl
<< strlen(arg) << std::endl;
}
void test(const char* format, ...) {
va_list ap;
va_start(ap, format);
for (int i = 0; i < 2; i++) {
parse(ap);
}
va_end(ap);
}
int main() {
test("hget %s %s", "abc", "123456");
}

32位机器的运行结果如下:

1
2
3
4
abc
3
abc
3

64位机器运行结果如下:

1
2
3
4
abc
3
123456
6

原因分析

出现上述结果的原因是由于va_list类型在32位和64位机器的类型不同导致的.

32位va_list

在32位上,va_list的定义为:

1
2
//注意,由于中间宏过多,这里省去了中间如_VA_LIST宏,直接给出实际定义。
typedef va_list char**;

64位va_list

在64位上va_list定义为一个结构体数组,并且数组中记录了可变参数被读的偏移量:

1
2
3
4
5
6
7
// Figure 3.34
typedef struct {
unsigned int gp_offset;
unsigned int fp_offset;
void *overflow_arg_area;
void *reg_save_area;
} va_list[1];

程序异常分析

当在32位机器上将va_list(char**)作为参数传递给函数的时候,该函数将从头开始读取该变长参数,还是使用va_list完毕并不记录当前va_list被读的偏移量,所以当第二次传入该va_list还是从头开始读取。

当在64为机器上将va_list(struct 数组)作为参数传递给函数的时候,该函数读取va_list完毕之后,将读取的偏移量记录在结构体中,由于其为数组传入函数,所以该被调用的函数改变了传入的va_list的偏移量。导致下次调用该函数从记录的偏移量开始读,造成不可预测或者内存越界等问题。

移植解决方案

将va_list初始化写到for循环内部,每次调用函数前都初始化va_list即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
void parse(va_list ap) {
char* arg;
arg = va_arg(ap, char*);
std::cout << arg << std::endl
<< strlen(arg) << std::endl;
}
void test(const char* format, ...) {
for (int i = 0; i < 2; i++) {
va_list ap;
va_start(ap, format);
parse(ap);
va_end(ap);
}
}
int main() {
test("hget %s %s", "abc", "123456");
}

参考:
https://stackoverflow.com/questions/4958384/what-is-the-format-of-the-x86-64-va-list-structure
http://blog.csdn.net/doubleface999/article/details/55798710

Comment and share

C中snprintf与vsnprintf函数

虽然snprintf函数在开发过程中比较常用,但是其中有一些细节性的问题需要注意。因为snprintf函数不是C中的标准函数,不同的编译器可能对该函数的实现不同,本文说明是基于GCC编译器。

snprintf函数

函数原型

snprintf函数的作用是将格式化的数据写入字符串。其函数原型如下:

1
snprintf(char* buffer, int n, char* format, ...);

参数说明

  • buffer : 存储格式化字符串的buffer
  • n : 指定格式化字符串的大小,包括\0
  • format : 指定需要格式化字符串的format
  • … : 可变参数

返回值

该函数的返回值为其期望字符串的长度,而不是实际存入buffer中的字符串的长度。且不包括\0

注意点

特别注意函数原型中的第二个参数包括\0的大小,而返回值为期望大小且不包括\0。

例:

1
2
3
4
5
6
7
8
char buffer[256];
//返回值ret = 13,buffer中的内容为123456789
int ret = snprintf(buffer, 10, "%s", "1234567890abc");
memset(buffer, 0x0, sizeof(buffer));
//返回值ret = 3,buffer中的内容为123
ret =snprintf(buffer, 10, "%s", "123");

snprintf与vsnprintf

snprintf和vsnprintf都是C语言printf家族函数的成员。实际上,snprintf和vsnprintf功能完全一样,只是vsnprintf将snprintf中的可变参数换成了av_list类型。如下:

1
2
3
4
5
#include <stdio.h>
int printf(const char* format, ...); //输出到标准输出
int fprintf(FILE* stream, const char* format, ...); //输出到文件
int sprintf(char* buffer, const char* format, ...); //输出到字符串
int snprintf(char* buffer, int n, const char* format, ...); //输出到字符串
1
2
3
4
5
#include <stdarg.h>
int vprintf(const char* format, va_list ap); //输出到标准输出
int vfprintf(FILE* stream, const char* format, va_list ap); //输出到文件
int vsprintf(char* buffer, const char* format, va_list ap); //输出到字符串
int vsnprintf(char* buffer, int n, const char* format, va_list ap); //输出到字符串

va_list获取

可变参数va_list获取方式通过下列函数获取,并且总是成对调用

1
2
va_start(va_list ap, last);
va_end(va_list ap);

简单的使用vsnprintf函数实现snprintf

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <stdarg.h>
int my_snprintf(char* buffer, int size, const char* format, ...) {
va_list ap;
va_start(ap, format);
int ret = vsnprintf(buffer, size, format, ap);
va_end(ap);
return ret;
}

Comment and share

  • page 1 of 1

魏传柳(2824759538@qq.com)

author.bio


Tencent


ShenZhen,China