某个函数返回的指针地址发生了变化,为什么?

veg*_*ego 0 c

抱歉,我无法发布任何可重现的代码,因为它是大型项目代码的一部分。

片段是

struct res_data *getRes(int id) {
  struct res_data *ret = malloc(sizeof(*ret));
  /*
  * res.aa = getAA(), etc...
  */

  // print ret got: 0x55ffb23ce000
  fprintf(stderr, "return: ret: %p\n", ret);
  return ret;
}
Run Code Online (Sandbox Code Playgroud)

然后

struct res_data *data = getRes(id);
fprintf(stderr, "got: %p\n", data);
// print data got: 0xffffffffb23ce000
Run Code Online (Sandbox Code Playgroud)

这是为什么?有什么可能的原因吗?

Jon*_*ler 5

在回答中转移评论。

诊断

我打赌你没有getRes()在定义它的地方和使用它的地方都使用的头文件中正确声明,所以使用它的地方认为它int来自32 位getRes(),然后它签名 -扩展以创建保存在data.

两个值的最后 8 个十六进制数字 (B23CE000) 相同,但原始指针getRes()在前 4 个字节中有一些零位,但在调用代码中的所有位均为 1,表示符号扩展。

道德:确保你有准确的原型。让你的编译器坚持他们。注意它的警告!

这是一些重现问题的代码 - 但对我来说却是毛骨悚然。我通常不会发布这样的代码。

main31.c

#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>

// Incorrect declaration, but what the compiler infers, more or less
// extern int getRes(int id);

int main(void)
{
    struct res_data *data = (struct res_data *)getRes(31);
    fprintf(stderr, "%6s: 0x%.16" PRIXPTR "\n", __func__, (uintptr_t)data);
    //free(data);    // free fails because the pointer is incorrect
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

getres31.c

#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>

struct res_data
{
    int    id;
    char   part2[32];
    double part3;
};

extern struct res_data *getRes(int id);

struct res_data *getRes(int id)
{
    struct res_data *ret = malloc(sizeof(*ret));
    ret->id = id;
    ret->part2[0] = '\0';
    ret->part3 = 0.0;
    fprintf(stderr, "%6s: 0x%.16" PRIXPTR "\n", __func__, (uintptr_t)ret);
    return ret;
}
Run Code Online (Sandbox Code Playgroud)

这段代码本质上是干净的,除了 的声明getRes()应该在getres31.c和 中包含的标头中main31.c

编译运行

通常,我使用-Werrorset 进行编译,因此警告会导致编译失败。

$ make so-6051-9209-a
gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -c -o main31.o main31.c
main31.c: In function ‘main’:
main31.c:10:48: warning: implicit declaration of function ‘getRes’; did you mean ‘gets’? [-Wimplicit-function-declaration]
   10 |     struct res_data *data = (struct res_data *)getRes(31);
      |                                                ^~~~~~
      |                                                gets
main31.c:10:29: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
   10 |     struct res_data *data = (struct res_data *)getRes(31);
      |                             ^
gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -c -o getres31.o getres31.c
gcc -o so-6051-9219-a -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes main31.o getres31.o
$
Run Code Online (Sandbox Code Playgroud)

运行时,输出会有所不同:

$ ./so-6051-9219-a
getRes: 0x00007FDF41D02620
  main: 0x0000000041D02620
$ ./so-6051-9219-a
getRes: 0x00007F868AC02AA0
  main: 0xFFFFFFFF8AC02AA0
$
Run Code Online (Sandbox Code Playgroud)

有时,接收到的值main()被解释为正数,有时被解释为负数,但该值是符号扩展的。

处方

解决方法是将 的声明getRes()放入头文件中,并将该头文件包含在两个源文件中。

main37.c

#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include "getres37.h"

int main(void)
{
    struct res_data *data = getRes(31);
    fprintf(stderr, "%6s: 0x%.16" PRIXPTR "\n", __func__, (uintptr_t)data);
    free(data);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

请注意,这次释放数据是安全的。

getres37.c

#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include "getres37.h"

struct res_data
{
    int    id;
    char   part2[32];
    double part3;
};

struct res_data *getRes(int id)
{
    struct res_data *ret = malloc(sizeof(*ret));
    ret->id = id;
    ret->part2[0] = '\0';
    ret->part3 = 0.0;
    fprintf(stderr, "%6s: 0x%.16" PRIXPTR "\n", __func__, (uintptr_t)ret);
    return ret;
}
Run Code Online (Sandbox Code Playgroud)

getres37.h

#ifndef GETRES37_H_INCLUDED
#define GETRES37_H_INCLUDED

extern struct res_data *getRes(int id);

#endif /* GETRES37_H_INCLUDED */
Run Code Online (Sandbox Code Playgroud)

编译运行

$ make so-6051-9219-b
gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -c -o main37.o main37.c
gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -c -o getres37.o getres37.c
gcc -o so-6051-9219-b -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes main37.o getres37.o
$
Run Code Online (Sandbox Code Playgroud)

没有警告!

$ ./so-6051-9219-b
getRes: 0x00007FBA11600700
  main: 0x00007FBA11600700
$ ./so-6051-9219-b
getRes: 0x00007FED23C02AA0
  main: 0x00007FED23C02AA0
$
Run Code Online (Sandbox Code Playgroud)

这一次,没有问题;中指针main()的值与 中的值相匹配getRes()

请注意,标头声明了一个不透明(不完整)类型struct res_data并声明了一个函数,该函数返回指向该类型的指针。该main()功能无法取消对它的引用返回指针-它不知道(或需要知道)结构的细节。这隐藏在包含getRes(). 这就是 C 支持信息隐藏的方式。使用不透明结构类型比有时看到的替代方案安全得多,后者在void *任何地方都使用。这是危险的,因为它不提供类型安全,不像不透明的结构类型。

另请注意,创建 MCVE(最小、完整、可验证示例)(或 MRE 或 SO 现在使用的任何名称)或 SSCCE(简短、自包含、正确示例)很容易。它真的根本不需要太多代码。但它确实让生活变得更轻松——我们不必做猜测!

JFTR:在运行 macOS Mojave 10.14.6 的 MacBook Pro 上进行测试,使用 GCC 9.2.0 和 Xcode 11.3.1。