昨天喝酒,今天一早上班就被同事叫过来一起看一个诡异的事情,其诡异程度一度让我怀疑自己还没酒醒。
问题是这样的:使用
libcurl
库,设置了
CURLOPT_HEADERFUNCTION
的回调,代码简化一下是这样的:
static size_t header_callback(char *buffer, size_t size, size_t nitems, void *userdata) {
Task *task = (Task *)userdata;
return task->receiveHeaders(buffer, nitems * size);
void Task::setup() {
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, this);
size_t Task::receiveHeaders(char *buffer, size_t bufferSize) {
int code = 0;
curl_easy_getinfo(_curl, CURLINFO_RESPONSE_CODE, &code);
if (code == 200) {
} else {
return bufferSize;
在Task::receiveHeaders
函数的 return bufferSize;
处打断点,查看传入的 bufferSize
参数,发现其值总是 0
,但是,在查看调用栈的上一帧 header_callback
处 nitems * size
明显不应该是0
。
面对如此诡异的问题,引起了我的极大兴趣。经过了多番调查研究,终于真相大白。
完全没有理由的怀疑 items * size
是不是计算错误了?于是做了一个修改,直接把这两个参数原样传递给receiveHeaders
,于是函数改成这样了:
receiveHeaders(char *buffer, size_t bufferSize, size_t size, size_t nitems) {
int code = 0;
curl_easy_getinfo(_curl, CURLINFO_RESPONSE_CODE, &code);
return bufferSize;
断点一看,好家伙,这次bufferSize
的计算结果正确的,size
的结果也跟传入参数一致,唯独是 nitems
参数变成0
了。
这个尝试得到一个结论:是最后一个参数莫名其妙变成了0
.
第二次尝试
也是完全没有理由的怀疑是函数调用入栈的问题,于是做了一个修改,把receiveHeaders
函数改成静态成员函数。由于静态成员函数不能访问成员变量,于是,顺手把所有业务代码都注释了,只返回 bufferSize.
于是函数改成了这样了:
receiveHeaders(char *buffer, size_t bufferSize, size_t size, size_t nitems) {
return bufferSize;
好家伙,问题消失了!最后一个参数正常了。
那么,问题看起来已经很清晰了,就是业务代码导致的问题。但是,业务代码到底是什么问题呢?业务代码完全没有修改参数值的地方。
第三次尝试
有了第二次尝试,误打误撞发现的业务代码问题之后,问题就好办了。函数恢复回成员函数定义,二分法注释部分代码逐一尝试。最终锁定就是开头的两行代码导致的:
int code = 0;
curl_easy_getinfo(_curl, CURLINFO_RESPONSE_CODE, &code);
这次不再头昏了,&code
这里的一个输出参数应该很明确的表示,最后一个参数很可能就是被这里写掉了。于是,
查看一下libcurl
中CURLINFO_RESPONSE_CODE
的定义:
typedef enum {
CURLINFO_NONE,
CURLINFO_EFFECTIVE_URL = CURLINFO_STRING + 1,
CURLINFO_RESPONSE_CODE = CURLINFO_LONG + 2,
作者真是一句多余的话也没说,但是,从= CURLINFO_LONG + 2
的定义方式,多少感觉到,这个getinfo
的输出参数类型应该是个long
而不应该是int
。翻libcurl
的线上手册,确认了这一点:
Example
CURL *curl = curl_easy_init();
if(curl) {
CURLcode res;
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com");
res = curl_easy_perform(curl);
if(res == CURLE_OK) {
long response_code;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
curl_easy_cleanup(curl);
把 int code = 0
改成 long code = 0
,问题解决。
为什么会这样呢?
这就要从函数的调用栈说起来:(网上随便搜的一个图,侵删)
调用receiveHeaders
时,调用栈从高地址到低地址,依次如下顺序排列
变量 | 类型 | 字节数(按64位平台讲述) |
---|
入参 this | intptr | 8 |
入参 buffer | char * | 8 |
入参 bufferSize | size_t | 8 |
入参 size | size_t | 8 |
入参 nitems | size_t | 8 |
局部变量 code | int | 4 |
由于局部变量code
定义的类型是int
占用的是4
字节的大小。
但是,传递给curl_easy_getinfo
后,函数是当成一个long
来处理,写入了8
字节的值,那么,其中高位的4
个字节就自然的写到了入参nitems
的低4个字节的内存位置了。这就是为什么总是最后一个参数变成0
的缘故了。
为了更好的理解上述原理,可以观看一下如下测试程序的输出:
#include <iostream>
using namespace std;
size_t func(size_t a, size_t b) {
cout << &a << endl;
cout << &b << endl;
int c = 10;
cout << &c << endl;
long *pc = (long *)&c;
*pc = 30;
cout << a << endl;
cout << b << endl;
cout << c << endl;
return a+b;
int main() {
func(1, 2);
return 0;
这个世界没有鬼,有的只是对无知的恐惧。