如果实现被覆盖,则删除死代码
Dead code removal if implementation is overwritten
我正在编写一个提供 sha256 实现的库。该库将提供给可能希望提供针对其平台优化的自己的 sha256 函数的供应商。因此,这个库的 API 允许客户端将函数指针传递给他们的 sha256 代码。
int mylib_set_sha256_impl( /* function pointers */ );
今后,所有算法都将使用内部提供的函数指针,而不是库提供的库存 sha256 代码。
问题是:如何在 link 时间内促进死代码的删除,从而删除此库中的默认 sha256 实现?
这是一个 API 设计问题,也是一个编译器优化问题。
对于链接器没有特殊要求,不需要考虑弱别名。
基本规则是:只要用户代码不引用您链接到库中的给定 .c
或 .s
文件中的任何符号,该文件的内容就不会t 最终出现在可执行文件中。
在您描述的场景中,您的函数可能永远不会成为死代码,因为您的 "live" 代码可能会引用它们的地址来设置默认函数指针值。为确保不会发生这种情况,您必须执行以下操作:
仅在 user-callable 可选 default_init
函数中引用可替换函数。
绝不能在代码中的任何地方调用 default_init
,也绝不能在 default_init
.
[=57 以外的任何地方引用任何函数=]
将可替换函数和 init 函数放入至少一个 .c
或未用于任何其他代码的汇编文件中。
为了让您的用户替换 所有 函数,他们只需永远不要调用 default_init
函数。如果您希望函数可替换 one-by-one,您还必须:
每个可替换函数都在其自己的 .c
文件中。
让用户不直接调用 default_init
,而是将所需的默认或 user-provided 实现传递给您的 init
函数。
实际上,您所做的并不是 "overwriting" 任何实现,而只是根本不使用它。
示例(包括为清楚起见而省略的守卫):
// api.h
void api_fun1(void);
void api_fun2(void);
void api_default_init(void);
void api_user_init(void (*f1)(void), void (*f2)(void));
void api_use_funs(void);
// api_internal.h
extern void (*api_f1)(void);
extern void (*api_f2)(void);
// common.c
#include "api.h"
#include "api_internal.h"
void (*api_f1)(void);
void (*api_f2)(void);
void api_user_init(void (*f1)(void), void (*f2)(void)) {
api_f1 = f1;
api_f2 = f2;
}
void api_use_funs(void) {
api_f1();
api_f2();
}
// api_fun1.c
#include "api.h"
void api_fun1(void) {}
// api_fun2.c
#include "api.h"
void api_fun2(void) {}
// api_default_init.c
#include "api.h"
#include "api_internal.h"
void api_default_init(void) {
api_f1 = api_fun1;
api_f2 = api_fun2;
}
假设用户想要用他们自己的覆盖 api_fun2
:
// main.c
#include "api.h"
#include <stdio.h>
void my_fun2() {
printf("%s\n", __FUNCTION__);
}
int main() {
api_user_init(api_fun1, my_fun2);
api_use_funs();
}
我正在编写一个提供 sha256 实现的库。该库将提供给可能希望提供针对其平台优化的自己的 sha256 函数的供应商。因此,这个库的 API 允许客户端将函数指针传递给他们的 sha256 代码。
int mylib_set_sha256_impl( /* function pointers */ );
今后,所有算法都将使用内部提供的函数指针,而不是库提供的库存 sha256 代码。
问题是:如何在 link 时间内促进死代码的删除,从而删除此库中的默认 sha256 实现?
这是一个 API 设计问题,也是一个编译器优化问题。
对于链接器没有特殊要求,不需要考虑弱别名。
基本规则是:只要用户代码不引用您链接到库中的给定 .c
或 .s
文件中的任何符号,该文件的内容就不会t 最终出现在可执行文件中。
在您描述的场景中,您的函数可能永远不会成为死代码,因为您的 "live" 代码可能会引用它们的地址来设置默认函数指针值。为确保不会发生这种情况,您必须执行以下操作:
仅在 user-callable 可选
default_init
函数中引用可替换函数。绝不能在代码中的任何地方调用
[=57 以外的任何地方引用任何函数=]default_init
,也绝不能在default_init
.将可替换函数和 init 函数放入至少一个
.c
或未用于任何其他代码的汇编文件中。
为了让您的用户替换 所有 函数,他们只需永远不要调用 default_init
函数。如果您希望函数可替换 one-by-one,您还必须:
每个可替换函数都在其自己的
.c
文件中。让用户不直接调用
default_init
,而是将所需的默认或 user-provided 实现传递给您的init
函数。
实际上,您所做的并不是 "overwriting" 任何实现,而只是根本不使用它。
示例(包括为清楚起见而省略的守卫):
// api.h
void api_fun1(void);
void api_fun2(void);
void api_default_init(void);
void api_user_init(void (*f1)(void), void (*f2)(void));
void api_use_funs(void);
// api_internal.h
extern void (*api_f1)(void);
extern void (*api_f2)(void);
// common.c
#include "api.h"
#include "api_internal.h"
void (*api_f1)(void);
void (*api_f2)(void);
void api_user_init(void (*f1)(void), void (*f2)(void)) {
api_f1 = f1;
api_f2 = f2;
}
void api_use_funs(void) {
api_f1();
api_f2();
}
// api_fun1.c
#include "api.h"
void api_fun1(void) {}
// api_fun2.c
#include "api.h"
void api_fun2(void) {}
// api_default_init.c
#include "api.h"
#include "api_internal.h"
void api_default_init(void) {
api_f1 = api_fun1;
api_f2 = api_fun2;
}
假设用户想要用他们自己的覆盖 api_fun2
:
// main.c
#include "api.h"
#include <stdio.h>
void my_fun2() {
printf("%s\n", __FUNCTION__);
}
int main() {
api_user_init(api_fun1, my_fun2);
api_use_funs();
}