【问题标题】:C api compatible with 32 and 64 bit integers simultaneouslyC api 同时兼容 32 位和 64 位整数
【发布时间】:2017-01-19 19:37:52
【问题描述】:

我有一个使用 int 类型的共享(或静态)库的 C api,并希望升级到 int64_t。 这样做,我想确保我以前的用户仍然可以升级库,而不必重写他们的整个代码。

我已经提出了关于这个要点的解决方案,它复制了我的代码行为(使用回调):https://git.io/vMy8G

example.c

// User defines a different type
#define MYLONG int

#include "interface.h"
#include "stdio.h"

// User callback using his own type
int test(const int i, const my_long var, const int j) {
    printf("i   = %d\n",i);
    printf("var = %lld\n",(long long) var);
    printf("j   = %d\n",j);
    printf("%lld\n", 2LL*var-11);
    return var - 7;
}

int main() {
    // This is what user sees
    api_func functionPtr = &test;
    define_callback(functionPtr);

    // Simulate callback call
    call_callback();
    return 0;
}

interface.h

#pragma once

// MYLONG is defined differently internally and externally (before importing this file)
typedef MYLONG my_long;

// Callback definition
typedef int (*api_func)(const int, const my_long, const int); // surround by int to test alignment issues

void define_callback(api_func fptr);
void call_callback();

internal.c

#define MYLONG long long

#include "interface.h"

// Callback handling
api_func callback_ptr = 0;

void define_callback(api_func fptr) {
    callback_ptr = fptr;
}

void call_callback() {
    (*callback_ptr)(1000, 100000, 100);
    (*callback_ptr)(1000, 10000000000, 100); // will overflow when user uses int
}

库将始终使用#define MYLONG int64_t 编译,而用户将使用#define MYLONG int64_t#define MYLONG int(这将根据其他一些设置自动完成)。最新的定义将确保向后兼容。

Valgrind 检查通过所有构建。

我的问题如下:

  • 安全吗?
  • 我是否依赖编译器的任何非保证行为?
  • 为什么它会(或不会)起作用? (规范中有关于这方面的任何段落吗?)
  • 您有什么更好的方法吗?

请注意,如果可能,我希望避免编写所有函数的 64 位版本。此外,这必须适用于 Linux (gcc)、Mac (gcc) 和 Windows (Visual Studio)。

【问题讨论】:

  • 编辑:我已经简化了这个问题。另请注意,此处的宏定义是为了提供一个简单的最小示例。这不会暴露给最终用户。
  • 你是对的。感谢您改进这篇文章。

标签: c api gcc platform


【解决方案1】:
  1. 安全吗?

没有。 example.c 中的用户将callback_ptr 设置为:

int test(const int i, const int var, const int j)

... 和call_callback() 称其为:

int (*api_func)(const int, const int64_t, const int)

只要intint64_t 的类型不同,这就是UB(不是溢出)。

  1. 我是否依赖编译器的任何非保证行为?

是的。

  1. 为什么它会(或不会)起作用? (规范中有关于这方面的任何段落吗?)

这是未定义的行为(UB)。代码正在调用具有一个签名的函数,但该函数可能具有不兼容的签名。

  1. 您有什么更好的方法吗?

是的。
A. 当然不要使用不匹配的函数签名进行编码。
B. 最后,我认为 OP 需要一种新方法。我建议添加一个函数define_callback64(),然后让用户像以前一样使用int (*f)(const int, int64_t, const int)define_callback() 调用define_callback64()。然后call_callback() 可以使用设置的那个。


代码cmets

请注意,如果可能,我希望避免编写所有函数的 64 位版本。

OP 想要“希望升级到 int64_t”但“避免编写我所有函数的 64 位版本”。这是最奇怪的,显然是矛盾的。

也许将internal.c重写为int64_t,然后调用select回调函数。

typedef int (*api_func)(const int, const my_long, const int) 中的 3 个 const 毫无用处。

OP 似乎假设 int 是 32 位给定的标题。最好编写不假设这一点的代码。 int至少是 16 位的。 OTOH,假设int 不比int64_t 宽,我认为这不是什么大问题,但即使是C 规范也没有定义。

OP 似乎还假设 long longint64_t 给定代码 #define MYLONG long long 但“库将始终使用 #define MYLONG int64_t 编译”。 OP 需要在代码和文档中正确且一致地使用类型。

【讨论】:

  • 感谢您的回答@chux。确实,long long 不一定与 int64_t 相同。我只是为了演示目的做了一个简单的例子。顺便说一句,如果标头可以包含多次(因此无法在此处创建初始化的全局变量),您将如何实现回调选择? [共享库需要知道用户决定使用哪个版本,除非有明确的函数调用,否则我看不出你如何“通知”它]
  • @mgb 同意,我只是为了简单的例子,但#define MYLONG long long 引入了不必要的不​​一致,因此对帖子进行了更复杂的审查。 #define MYLONG int64_t 会同意帖子的文字。 OTOH,也许你真的想要long long,而不一定是固定的 64 位整数。编码目标是什么:long longint64_t,或者,其他什么?也许去intmax_t?很多选择。
  • “共享库需要知道用户决定使用哪个版本,除非有明确的函数调用,否则我看不出你如何“通知”它”--> 对。 “internal.c”代码可以通过注意什么函数被称为define_callback()define_callback64()define_callback_longlong() 等来知道需要什么类型。其中一个函数在任何call_callback() 之前只会被调用一次。替代方案:如果使用 C99 或更高版本,代码可以探索使用 _Generic
  • 感谢@chux 的帮助,但正如我所说,出于兼容性目的,我不希望用户重新定义他们定义的回调调用。开关必须对他们来说很顺畅。我可以根据用户架构选择使用宏重新定义函数名称,但出于调试目的我不喜欢它。
【解决方案2】:

编写最佳移植代码的最佳方法是不要依赖大小。如果你明确使用uint32_tint64_t

使用原生 intlong 否则。

要求用户为完全不需要的东西定义宏很容易出错。

【讨论】:

  • 我当前的 api 使用 int (即使在 64 位系统上也是 32 位),我想使用 int64_t 代替,因为它需要处理一些更大的问题。如果我切换到 int64_t 这将为用户带来大量不必要的警告。请注意,该示例并未说明最终的 api,而只是简化了问题。我不会要求用户定义一个宏来从一个模型切换到另一个模型。
猜你喜欢
  • 2023-03-30
  • 2013-03-12
  • 2010-09-07
  • 2010-10-21
  • 1970-01-01
  • 2021-02-08
  • 1970-01-01
  • 1970-01-01
  • 2013-10-21
相关资源
最近更新 更多