TLDR:
- 介绍了为什么不能将
__inline__
的效果了解为 宏打开。__inline__
并不会改动 部分 static 变量本来的行为,因为编译器将最终决议是否进行 inline ,编译器自然应该抹平差异。当然你假如还增加了__attribute__((always_inline))
就一定会内联。- 尽量不要在 .h 中声明 static 函数(包括 NS_INLINE 函数),除非你很清楚你在做什么。
问题最早是最近在看公司内某个 DI 库 ,学到了非常多有趣的设计与设计理念,但一起也遇到了一些疑问,例如这个 XXXIsBlock 。
// XXXServiceKitDefines.h
NS_INLINE BOOL XXXIsBlock(id _Nullable block) {
static Class blockClass;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
blockClass = [^{} class];
while ([blockClass superclass] != NSObject.class) {
blockClass = [blockClass superclass];
}
});
return [block isKindOfClass:blockClass];
}
函数逻辑很简略,判断传入的参数是否是 blockClass
,其间获取 blockClass
的逻辑只需求履行一次。
这一起仍是一个 Inline 的函数,咱们知道在正常情况下,标注了 NS_INLINE 的函数,还会默许带有 static 的属性。而我一直将 NS_INLINE 的行为按宏界说了解。
#if !defined(NS_INLINE)
#if defined(__GNUC__)
#define NS_INLINE static __inline__ __attribute__((always_inline)) // 走的这一行
#elif defined(__MWERKS__) || defined(__cplusplus)
#define NS_INLINE static inline
#elif defined(_MSC_VER)
#define NS_INLINE static __inline
#endif
#endif
一般咱们用 __inline__
就能标记咱们期望这个函数是以内联方法存在的,可是光是运用这个是不能确认最终函数是否被内联成功,编译器会归纳考虑这一点。因而还需求增加 __attribute__((always_inline))
,来告知编译器,咱们就要是内联!
而这个函数体内界说了部分的 static 变量 onceToken ,那假如这个函数被 inline 了屡次,岂不是 onceToken 的维护失效了?
怀着这个忧虑,我写了一个 Demo ,新鲜的面试题呈现了,环境为 Xcode 15.0 , C++ version 为 GNU++14,输出怎么?
这儿其有用 backtrace(基于 fp) 来 栈回溯 跟用 libunwind 的输出会有不同,运用 libunwind 会更准。对栈回溯感兴趣的能够看C++ exception handling ABI的内容。
dispatch_once()
简略来说便是做了一个判断,假如 onceToken != ~0l
(其实是 -1),就没进过这个 Block ,需求履行;否则就不必履行了。
void
_dispatch_once(dispatch_once_t *predicate,
DISPATCH_NOESCAPE dispatch_block_t block)
{
if (DISPATCH_EXPECT(*predicate, ~0l) != ~0l) {
dispatch_once(predicate, block);
} else {
dispatch_compiler_barrier();
}
DISPATCH_COMPILER_CAN_ASSUME(*predicate == ~0l);
}
Demo
// Warpper.h
void printCallerForDispatchOnce(void) {
int depth = 4;
void *callstack[depth];
int frames = backtrace(callstack, depth);
char **symbols = backtrace_symbols(callstack, frames);
NSLog(@"%s", symbols[depth - 1]); // 其实仅仅为了输出 HCZIsBlock() 的 caller
free(symbols); // 不要忘了 free ,否则就 LEAKS 了
}
NS_INLINE BOOL HCZIsBlock(id _Nullable block) {
static Class blockClass;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
printCallerForOuter();
// 为了后文剖析便利,不再放出实践履行代码
// blockClass = [^{} class];
// while ([blockClass superclass] != NSObject.class) {
// blockClass = [blockClass superclass];
// }
});
// // 为了后文剖析便利,不再放出实践履行代码
// return [block isKindOfClass:blockClass];
return NO;
}
// AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
HCZIsBlock(nil);
return YES;
}
// ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
[self testVoid];
[self testFloat];
}
- (void)testVoid {
HCZIsBlock(nil);
// NSLog(@"testVoid 履行成功 n==========");
}
- (CGFloat)testFloat {
HCZIsBlock(nil);
// NSLog(@"testFloat 履行成功");
return 3.f;
}
答案:输出了两遍,分别是 AppDelegate.m 里一遍,ViewController.m 里先履行的办法一遍(这个 Demo 里是 testVoid 办法)。
3 RuntimeTest 0x000000010274d514 -[AppDelegate application:didFinishLaunchingWithOptions:] + 164
3 RuntimeTest 0x000000010274cfb0 -[ViewController testVoid] + 124
因而咱们知道了,确实应该只被履行一次的代码却被履行了屡次。因而这么写是有危险的。可是问题没这么简略,咱们总共调用了三次,却只要两次输出,阐明还有其他约束。需求一探终究。
Static 函数中的 local static variable
同一个 Compile Unit
(lldb) dis
RuntimeTest`-[ViewController testVoid]:
0x1009f119c <+0>: sub sp, sp, #0x40
0x1009f11a0 <+4>: stp x29, x30, [sp, #0x30]
0x1009f11a4 <+8>: add x29, sp, #0x30
0x1009f11a8 <+12>: str x0, [sp, #0x10]
0x1009f11ac <+16>: str x1, [sp, #0x8]
0x1009f11b0 <+20>: add x0, sp, #0x18
0x1009f11b4 <+24>: mov x1, #0x0
0x1009f11b8 <+28>: str xzr, [sp, #0x18]
0x1009f11bc <+32>: bl 0x1009f1d78 ; symbol stub for: objc_storeStrong
0x1009f11c0 <+36>: adrp x8, 8
0x1009f11c4 <+40>: add x8, x8, #0x780 ; HCZIsBlock.onceToken
0x1009f11c8 <+44>: stur x8, [x29, #-0x8]
0x1009f11cc <+48>: sub x0, x29, #0x10
0x1009f11d0 <+52>: stur xzr, [x29, #-0x10]
0x1009f11d4 <+56>: adrp x1, 3
0x1009f11d8 <+60>: add x1, x1, #0x38 ; __block_literal_global
0x1009f11dc <+64>: bl 0x1009f1d78 ; symbol stub for: objc_storeStrong
0x1009f11e0 <+68>: ldur x8, [x29, #-0x8]
0x1009f11e4 <+72>: ldr x8, [x8]
0x1009f11e8 <+76>: adds x8, x8, #0x1
0x1009f11ec <+80>: cset w8, eq
0x1009f11f0 <+84>: tbnz w8, #0x0, 0x1009f1208 ; <+108> [inlined] _dispatch_once + 40 at once.h:87:3
0x1009f11f4 <+88>: b 0x1009f11f8 ; <+92> [inlined] _dispatch_once + 24 at once.h:85:17
0x1009f11f8 <+92>: ldur x0, [x29, #-0x8]
0x1009f11fc <+96>: ldur x1, [x29, #-0x10]
0x1009f1200 <+100>: bl 0x1009f1d90 ; symbol stub for: dispatch_once
-> 0x1009f1204 <+104>: b 0x1009f120c ; <+112> [inlined] _dispatch_once + 44 at once.h
0x1009f1208 <+108>: b 0x1009f120c ; <+112> [inlined] _dispatch_once + 44 at once.h
0x1009f120c <+112>: sub x0, x29, #0x10
0x1009f1210 <+116>: mov x1, #0x0
0x1009f1214 <+120>: str x1, [sp]
0x1009f1218 <+124>: bl 0x1009f1d78 ; symbol stub for: objc_storeStrong
0x1009f121c <+128>: ldr x1, [sp]
0x1009f1220 <+132>: add x0, sp, #0x18
0x1009f1224 <+136>: bl 0x1009f1d78 ; symbol stub for: objc_storeStrong
0x1009f1228 <+140>: ldp x29, x30, [sp, #0x30]
0x1009f122c <+144>: add sp, sp, #0x40
0x1009f1230 <+148>: ret
在 -[ViewController testVoid]
函数中,咱们首要关注 HCZIsBlock.onceToken
的寻址,看是否找到了同一个。
0x1009f11c0 <+36>: adrp x8, 8
0x1009f1000 + 0x8000 = 0x1009f9000,因而最终 x8 里存着:0x1009f9000 。
关于 ADRP 核算不熟悉的同学能够看:ADRP 核算示例。
0x1009f11c4 <+40>: add x8, x8, #0x780 ; HCZIsBlock.onceToken
x8 自增 0x780 ,也便是 0x1009f9000 + 0x780 = 0x1009f9780 ,这时分 x8 其实便是 intptr_t * ,等于是一个指针。
0x1009f11c8 <+44>: stur x8, [x29, #-0x8]
把 x8 寄存到了 x29 – 0x8 的位置,也便是 栈上。x29 便是 frame pointer(fp)。
0x1009f11e0 <+68>: ldur x8, [x29, #-0x8]
经过一系列与 onceToken 的无关核算后,又从 x29 – 0x8 的位置从头取出了 intptr_t * 的值并从头塞回了 x8 。
0x1009f11e4 <+72>: ldr x8, [x8]
把 intptr_t * 的值求引用,也便是 x8 = *(x8) ,也便是实践的 intptr_t 类型的 static dispatch_once_t onceToken 了。
0x1009f11e8 <+76>: adds x8, x8, #0x1
0x1009f11ec <+80>: cset w8, eq
0x1009f11f0 <+84>: tbnz w8, #0x0, 0x1009f1208 ; <+108> [inlined] _dispatch_once + 40 at once.h:87:3
0x1009f11f4 <+88>: b 0x1009f11f8 ; <+92> [inlined] _dispatch_once + 24 at once.h:85:17
对 onceToken
进行核算,假如 onceToken
在 + 0x1
之后是 0x0 的话,跳转到 0x1009f11f8,履行被维护的代码;不是的话跳转到 0x1009f1208 ,跳过被维护的代码。
在履行完被维护的代码后,重置 onceToken
的逻辑在 _dispatch_once_callout
中,实践是在 _dispatch_once_mark_quiescing
中,因为 inline 的联系,调用栈不一定能看到。这儿就不走进去剖析了。
DISPATCH_NOINLINE
static void
_dispatch_once_callout(dispatch_once_gate_t l, void *ctxt,
dispatch_function_t func)
{
_dispatch_client_callout(ctxt, func);
_dispatch_once_gate_broadcast(l);
}
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_once_gate_broadcast(dispatch_once_gate_t l)
{
dispatch_lock value_self = _dispatch_lock_value_for_self();
uintptr_t v;
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
v = _dispatch_once_mark_quiescing(l); // 走的这一行
#else
v = _dispatch_once_mark_done(l);
#endif
if (likely((dispatch_lock)v == value_self)) return;
_dispatch_gate_broadcast_slow(&l->dgo_gate, (dispatch_lock)v);
}
DISPATCH_ALWAYS_INLINE
static inline uintptr_t
_dispatch_once_mark_quiescing(dispatch_once_gate_t dgo)
{
return os_atomic_xchg(&dgo->dgo_once, _dispatch_once_generation(), release);
}
再看看 -[ViewController testFloat] 中的代码,大部份逻辑都是相同的,咱们只剖析取 HCZIsBlock.onceToken 的部分。
(lldb) dis
RuntimeTest`-[ViewController testFloat]:
0x1009f1234 <+0>: sub sp, sp, #0x40
0x1009f1238 <+4>: stp x29, x30, [sp, #0x30]
0x1009f123c <+8>: add x29, sp, #0x30
0x1009f1240 <+12>: str x0, [sp, #0x10]
0x1009f1244 <+16>: str x1, [sp, #0x8]
0x1009f1248 <+20>: add x0, sp, #0x18
0x1009f124c <+24>: mov x1, #0x0
0x1009f1250 <+28>: str xzr, [sp, #0x18]
0x1009f1254 <+32>: bl 0x1009f1d78 ; symbol stub for: objc_storeStrong
0x1009f1258 <+36>: adrp x8, 8
0x1009f125c <+40>: add x8, x8, #0x780 ; HCZIsBlock.onceToken
0x1009f1260 <+44>: stur x8, [x29, #-0x8]
0x1009f1264 <+48>: sub x0, x29, #0x10
0x1009f1268 <+52>: stur xzr, [x29, #-0x10]
0x1009f126c <+56>: adrp x1, 3
0x1009f1270 <+60>: add x1, x1, #0x38 ; __block_literal_global
0x1009f1274 <+64>: bl 0x1009f1d78 ; symbol stub for: objc_storeStrong
0x1009f1278 <+68>: ldur x8, [x29, #-0x8]
0x1009f127c <+72>: ldr x8, [x8]
0x1009f1280 <+76>: adds x8, x8, #0x1
0x1009f1284 <+80>: cset w8, eq
0x1009f1288 <+84>: tbnz w8, #0x0, 0x1009f12a0 ; <+108> [inlined] _dispatch_once + 40 at once.h:87:3
0x1009f128c <+88>: b 0x1009f1290 ; <+92> [inlined] _dispatch_once + 24 at once.h:85:17
0x1009f1290 <+92>: ldur x0, [x29, #-0x8]
0x1009f1294 <+96>: ldur x1, [x29, #-0x10]
0x1009f1298 <+100>: bl 0x1009f1d90 ; symbol stub for: dispatch_once
0x1009f129c <+104>: b 0x1009f12a4 ; <+112> [inlined] _dispatch_once + 44 at once.h
0x1009f12a0 <+108>: b 0x1009f12a4 ; <+112> [inlined] _dispatch_once + 44 at once.h
0x1009f12a4 <+112>: sub x0, x29, #0x10
0x1009f12a8 <+116>: mov x1, #0x0
0x1009f12ac <+120>: str x1, [sp]
0x1009f12b0 <+124>: bl 0x1009f1d78 ; symbol stub for: objc_storeStrong
0x1009f12b4 <+128>: ldr x1, [sp]
0x1009f12b8 <+132>: add x0, sp, #0x18
0x1009f12bc <+136>: bl 0x1009f1d78 ; symbol stub for: objc_storeStrong
0x1009f12c0 <+140>: fmov d0, #3.00000000
-> 0x1009f12c4 <+144>: ldp x29, x30, [sp, #0x30]
0x1009f12c8 <+148>: add sp, sp, #0x40
0x1009f12cc <+152>: ret
0x1009f1258 <+36>: adrp x8, 8
0x1009f1000 + 0x8000 = 0x1009f9000 ,因而最终 x8 里存着:0x1009f9000
0x1009f125c <+40>: add x8, x8, #0x780 ; HCZIsBlock.onceToken
x8 自增 0x780 ,也便是 0x1009f9000 + 0x780 = 0x1009f9780
咱们能够看到两个函数里拿到的的 HCZIsBlock.onceToken 都是同一个。
因而在同一个 Compile Unit 内,NS_INLINE(static inline) 函数中的部分 static 变量都是同一个。
不同的 Compile Unit
-[AppDelegate application:didFinishLaunchingWithOptions:]
在另一个 Compile Unit(.m) 中,咱们也来看看这儿引用的 HCZIsBlock.onceToken 的地址。
(lldb) dis
RuntimeTest`-[AppDelegate application:didFinishLaunchingWithOptions:]:
0x104c5d5c8 <+0>: sub sp, sp, #0x60
0x104c5d5cc <+4>: stp x29, x30, [sp, #0x50]
0x104c5d5d0 <+8>: add x29, sp, #0x50
0x104c5d5d4 <+12>: mov x8, x1
0x104c5d5d8 <+16>: mov x1, x2
0x104c5d5dc <+20>: str x3, [sp, #0x8]
0x104c5d5e0 <+24>: stur x0, [x29, #-0x20]
0x104c5d5e4 <+28>: str x8, [sp, #0x28]
0x104c5d5e8 <+32>: add x0, sp, #0x20
0x104c5d5ec <+36>: mov x8, #0x0
0x104c5d5f0 <+40>: str x8, [sp, #0x10]
0x104c5d5f4 <+44>: str xzr, [sp, #0x20]
0x104c5d5f8 <+48>: bl 0x104c5dd78 ; symbol stub for: objc_storeStrong
0x104c5d5fc <+52>: ldr x1, [sp, #0x8]
0x104c5d600 <+56>: add x0, sp, #0x18
0x104c5d604 <+60>: str xzr, [sp, #0x18]
0x104c5d608 <+64>: bl 0x104c5dd78 ; symbol stub for: objc_storeStrong
0x104c5d60c <+68>: ldr x1, [sp, #0x10]
0x104c5d610 <+72>: sub x0, x29, #0x18
0x104c5d614 <+76>: stur xzr, [x29, #-0x18]
0x104c5d618 <+80>: bl 0x104c5dd78 ; symbol stub for: objc_storeStrong
0x104c5d61c <+84>: adrp x8, 8
0x104c5d620 <+88>: add x8, x8, #0x788 ; HCZIsBlock.onceToken
0x104c5d624 <+92>: stur x8, [x29, #-0x8]
0x104c5d628 <+96>: sub x0, x29, #0x10
0x104c5d62c <+100>: stur xzr, [x29, #-0x10]
0x104c5d630 <+104>: adrp x1, 3
0x104c5d634 <+108>: add x1, x1, #0x58 ; __block_literal_global
0x104c5d638 <+112>: bl 0x104c5dd78 ; symbol stub for: objc_storeStrong
0x104c5d63c <+116>: ldur x8, [x29, #-0x8]
0x104c5d640 <+120>: ldr x8, [x8]
0x104c5d644 <+124>: adds x8, x8, #0x1
0x104c5d648 <+128>: cset w8, eq
0x104c5d64c <+132>: tbnz w8, #0x0, 0x104c5d664 ; <+156> [inlined] _dispatch_once + 40 at once.h:87:3
0x104c5d650 <+136>: b 0x104c5d654 ; <+140> [inlined] _dispatch_once + 24 at once.h:85:17
0x104c5d654 <+140>: ldur x0, [x29, #-0x8]
0x104c5d658 <+144>: ldur x1, [x29, #-0x10]
0x104c5d65c <+148>: bl 0x104c5dd90 ; symbol stub for: dispatch_once
0x104c5d660 <+152>: b 0x104c5d668 ; <+160> [inlined] _dispatch_once + 44 at once.h
0x104c5d664 <+156>: b 0x104c5d668 ; <+160> [inlined] _dispatch_once + 44 at once.h
0x104c5d668 <+160>: sub x0, x29, #0x10
0x104c5d66c <+164>: mov x1, #0x0
0x104c5d670 <+168>: str x1, [sp]
0x104c5d674 <+172>: bl 0x104c5dd78 ; symbol stub for: objc_storeStrong
0x104c5d678 <+176>: ldr x1, [sp]
0x104c5d67c <+180>: sub x0, x29, #0x18
0x104c5d680 <+184>: bl 0x104c5dd78 ; symbol stub for: objc_storeStrong
0x104c5d684 <+188>: ldr x1, [sp]
0x104c5d688 <+192>: add x0, sp, #0x18
0x104c5d68c <+196>: bl 0x104c5dd78 ; symbol stub for: objc_storeStrong
0x104c5d690 <+200>: ldr x1, [sp]
0x104c5d694 <+204>: add x0, sp, #0x20
0x104c5d698 <+208>: bl 0x104c5dd78 ; symbol stub for: objc_storeStrong
0x104c5d69c <+212>: mov w8, #0x1
0x104c5d6a0 <+216>: and w0, w8, #0x1
0x104c5d6a4 <+220>: ldp x29, x30, [sp, #0x50]
0x104c5d6a8 <+224>: add sp, sp, #0x60
0x104c5d6ac <+228>: ret
0x104c5d61c <+84>: adrp x8, 8
0x104c5d620 <+88>: add x8, x8, #0x788 ; HCZIsBlock.onceToken
0x104c5d000 + 0x8000 + 0x788 = 0x104C65788
核算方法同上,能够看到,在 -[AppDelegate application:didFinishLaunchingWithOptions:]
办法里核算出的 HCZIsBlock.onceToken 地址变成了 0x104C65788,显着与 -[ViewController testFloat]
/ -[ViewController testVoid]
的不是一个。
因而在不同 Compile Unit 内,NS_INLINE(static inline) 函数中的部分 static 变量不是同一个。
关于 static 函数,不管是否有 __attribute((always_inline))__
润饰,都不影响内部的 static 变量只对当时的 compile unit 可见,因而会有许多份 static 变量存在。
Static 函数的结论
// #define NS_INLINE static __inline__ __attribute__((always_inline))
NS_INLINE BOOL HCZIsBlock(id _Nullable block) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
return NO;
}
static BOOL HCZStaticFunc(id _Nullable block) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
return NO;
}
- static 函数内的 部分 static 变量虽然在语义上是部分可见,但其实是 global hidden variable ,hidden 的意思是对其他的 Compile Unit 不可见。
- Static 函数在每个 Compile Unit 中都会有一份自己的 copy,因而在不同的 Compile Unit 中,static 函数内的 部分 static 变量 也会有多份。不管 inline 与否。
- 但在同一个 Compile Unit 中,static 函数内的 部分 static 变量 只要一份,也就呈现了上面在 ViewController.m 中两个 ,即便 inline 也是如此。
- 假如将 static 函数界说在 .h 中,每一个 import 这个 .h 的 Compile Unit 都会包括一份这个 static 函数的副本,这个行为或许导致非预期的包大小增长,不做引荐。(即:别在 .h 里界说 static 函数)。
回到最初的问题
因而回到最开端的问题,需求确保 HCZIsBlock 中被维护的代码只被履行一遍有两个选项:
- 将 NS_INLINE HCZIsBlock 的界说挪到 .m 中,只要当时 .m 可见的话,也就能确保只在同一个 Compile Unit 运用。
- 将 HCZIsBlock 的界说改为
__inline__ __attribute((always_inline))__
,即不再声明为 static 函数。
因而我认为这儿的问题在于不应该将这个函数界说为 static 函数,而是否 __inline__ __attribute__((always_inline))
则都能够,看设计考虑即可。
公司外也都还有一些代码也有相同的问题,考虑后续增加一些静态检查管理增量的问题。@Kyle-Ye 已对 swift-corelibs-foundation 上游提了 issue 。
假如是一个一般的 __attribute((always_inline))__
函数中的 static 变量呢?
但只到这儿,其实问题还没有剖析完,上面咱们剖析的都是 static 函数的情况,那假如是正常的函数呢?
C 的函数默许都是 extern 的(与 static 函数对应)。
咱们猜想一下,正常的函数既然没有了 static 的语义,那就意味着不对外躲藏,因而全局只要一个 hidden global 变量。
那叠加 inline 会改动这个结果吗?
// .h
void normalPrintCallerForDispatchOnce(void);
BOOL XXXNormalExternFunction(void);
BOOL __inline__ __attribute__((always_inline)) XXXNormalInlineExternFunction(void) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
normalPrintCallerForDispatchOnce();
});
return NO;
}
// .m
void normalPrintCallerForDispatchOnce(void) {
int depth = 4;
void *callstack[depth];
int frames = backtrace(callstack, depth);
char **symbols = backtrace_symbols(callstack, frames);
NSLog(@"%s", symbols[depth - 1]);
free(symbols);
}
BOOL XXXNormalExternFunction(void) {
// 这个大家都很容易了解只要一份 onceToken
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
normalPrintCallerForDispatchOnce();
});
return NO;
}
在不同的 Compile Unit 中调用,在相同的 Compile Unit 中调用,都能够让维护区域内的代码只履行一次。
咱们本地写个 demo 能够看到,不管是否 always_inline ,其间的 static 变量都是同一份。咱们也能在 C++ 标准中找到如下描绘:
A static local variable in an extern inline function always refers to the same object. 7.1.2/4 – C++98/C++14 (n3797)
一般函数的结论
正常的函数(非 static ),不管是否 attribute((always_inline)) ,其间的 local static 变量,都只要一份。
那 __inline__
到底表明了什么呢?
本文发布后,有同学互动的时分提出了一个很有趣的问题:
请教下,头文件顶用inline润饰的函数,被多个文件重复包括头文件时,不会呈现重复界说的问题,但inline不确保内联成功,那么inline润饰影响了什么地方,从而防止的这个问题呢?
要答复这个问题, 咱们就要回忆 __inline__
的历史了。
inline let’s you define a function in a header file that is included by multiple other .cpp files. If you don’t specify inline on that function each .cpp file that includes that header will compile the function and put a symbol for it in the generated object file. When you then link all the multiple object files together to form your executable or library you get the linker error: “multiple definitions of function XXX”, because you violated the One Definition Rule. The linker will need to know which function to call, but if you have multiple functions with the same name it doesn’t know they are identical and it can’t choose one over the other – thus the error. Now, if you mark a function inline you tell the linker: “there will be multiple instances of this function, but I swear: they are all identical and you can just link against which ever one of you prefer and throw away the rest”. A better keyword would have been e.g. unique or similar, but it’s hard to choose names and the mistake has already been made. www.reddit.com/r/cpp_quest…
这个答复的大意是:inline 是为了解决议义在 .h 中的函数被多个 .cpp(Compile Unit) 文件引进之后,会呈现重复的多份,因为 Symbol 是彻底相同的,linker 会不知道挑选哪一份,就会在 link 的阶段报错。加了 inline 之后,linker 就能够随意挑选一份保留了,就不会再有重复的 Symbol 呈现了。因而更适合 inline 真实语义的表述应该是 unique 或许 similar 。
在知道了 __inline__
真实恰当的语义之后,咱们就能够看加上 __inline__
(假定强制 inline 成功)之后到底对 Symbol 做了什么了。
➜ RuntimeTest git:(main) ✗ nm /Users/bytedance/Desktop/RuntimeTest/ViewController.o // 部分内容,非悉数输出
00000000000026e8 b _HCZIsBlock.blockClass
00000000000026f0 b _HCZIsBlock.onceToken
0000000000000154 t _HCZStaticFunc
00000000000026f8 b _HCZStaticFunc.onceToken
0000000000002700 b _XXXNormalInlineExternFunction.onceToken
00000000000002e8 t ___HCZIsBlock_block_invoke
00000000000003e8 t ___HCZStaticFunc_block_invoke
0000000000000400 t ___XXXNormalInlineExternFunction_block_invoke
U _normalPrintCallerForDispatchOnce
➜ RuntimeTest git:(main) ✗ nm -a /Users/bytedance/Desktop/RuntimeTest/AppDelegate.o // 部分内容,非悉数输出
0000000000004748 b _HCZIsBlock.blockClass
0000000000004750 b _HCZIsBlock.onceToken
000000000000014c t _HCZStaticFunc
0000000000004758 b _HCZStaticFunc.onceToken
0000000000004760 b _XXXNormalInlineExternFunction.onceToken
000000000000033c t ___HCZIsBlock_block_invoke
000000000000043c t ___HCZStaticFunc_block_invoke
0000000000000454 t ___XXXNormalInlineExternFunction_block_invoke
U _normalPrintCallerForDispatchOnce
咱们能够看到,标注为 inline (假定强制 inline 成功)的函数,都没有单独的符号输出,可是其间的 local static variable 都呈现了。
两个 .o 中都有的符号,且有地址,意思是每个 compile unit 都有一份自己的 copy ,例如 _HCZIsBlock.blockClass
/ _HCZStaticFunc.onceToken
等;仅有的不同的是 _normalPrintCallerForDispatchOnce
,仅仅一个一般的 extern 函数,当时编译单元没法清晰,因而类型是 undefined 。
manpages.ubuntu.com/manpages/fo…
“b” The symbol is in the BSS data section. This section typically contains zero-initialized or uninitialized data, although the exact behavior is system dependent.
“t” The symbol is in the text (code) section.
“U” The symbol is undefined.
“w” The symbol is a weak symbol that has not been specifically tagged as a weak object symbol. When a weak defined symbol is linked with a normal defined symbol, the normal defined symbol is used with no error. When a weak undefined symbol is linked and the symbol is not defined, the value of the symbol is determined in a system-specific manner without error. On some systems, uppercase indicates that a default value has been specified.
这也就能辅佐咱们验证上面的结论了。
那假如 __inline__
可是 inline 失败呢?根据 The real meaning of inline (for free functions) 中的说法,会变成 weak symbol ,而 weak symbol 是能够存在多份的,不会引起 link error。
The keyword inline is really for creating weak symbols that gets unified at link time, and this will be even more important for variables.
关于 __inline__
的更多危险能够检查:Dangers of linking inline functions 。简略归纳便是因为 weak symbol 的联系,最终假如有一份 strong symbol 的话,会变成那一份 strong symbol,详细的完成或许会在杂乱工程中被替换掉,所见不所得。
结论:
-
__inline__ __attribute__((always_inline))
并不会改动 部分 static 变量本来的行为,因而行为自身仍是需求检查函数的界说是否带 static 。因而将__inline__ __attribute__((always_inline))
粗浅得了解为与宏打开共同,是有失偏颇的。仔细想想,这也是合理的,究竟假如不增加__attribute__((always_inline))
,咱们是把挑选是否内联的决策权交给编译器,编译器的行为自然应该抹平一起差异。
a. Static 函数中的 static 变量,同一个 Compile Unit 同享一份,不同 Compile Unit 不同享一份
b. 正常函数中的 static 变量,全都同享一份
-
被标记为 NS_INLINE 的函数,也就隐式变成了 static 函数,需求特别注意。(也要问清楚是
__inline__ __attribute__((always_inline))
仍是NS_INLINE
) 。 -
尽量不要在 .h 中声明 static 函数,除非你很清楚你在做什么。
参考链接:
stackoverflow.com/questions/1…
友情链接
iOS开发工程师-抖音中视频-北京/上海 正在招聘,大佬们都看到这儿了不考虑来看看嘛!