iOS-底層原理 05:內(nèi)存對(duì)齊原理

iOS 底層原理 文章匯總

在探討內(nèi)存對(duì)齊原理之前,首先介紹下iOS中獲取內(nèi)存大小的三種方式

獲取內(nèi)存大小的三種方式

獲取內(nèi)存大小的三種方式分別是:

  • sizeof
  • class_getInstanceSize
  • malloc_size

sizeof

  • 1、sizeof是一個(gè)操作符,不是函數(shù)
  • 2、我們一般用sizeof計(jì)算內(nèi)存大小時(shí),傳入的主要對(duì)象是數(shù)據(jù)類(lèi)型,這個(gè)在編譯器的編譯階段(即編譯時(shí))就會(huì)確定大小而不是在運(yùn)行時(shí)確定。
  • 3、sizeof最終得到的結(jié)果是該數(shù)據(jù)類(lèi)型占用空間的大小

class_getInstanceSize

這個(gè)方法在iOS-底層原理 02:alloc & init & new 源碼分析分析時(shí)就已經(jīng)分析了,是runtime提供的api,用于獲取類(lèi)的實(shí)例對(duì)象所占用的內(nèi)存大小,并返回具體的字節(jié)數(shù),其本質(zhì)就是獲取實(shí)例對(duì)象中成員變量的內(nèi)存大小

malloc_size

這個(gè)函數(shù)是獲取系統(tǒng)實(shí)際分配的內(nèi)存大小

可以通過(guò)下面代碼的輸出結(jié)果來(lái)驗(yàn)證我們上面的說(shuō)法

#import <Foundation/Foundation.h>
#import "LGPerson.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *objc = [[NSObject alloc] init];
        NSLog(@"objc對(duì)象類(lèi)型占用的內(nèi)存大小:%lu",sizeof(objc));
        NSLog(@"objc對(duì)象實(shí)際占用的內(nèi)存大小:%lu",class_getInstanceSize([objc class]));
        NSLog(@"objc對(duì)象實(shí)際分配的內(nèi)存大小:%lu",malloc_size((__bridge const void*)(objc)));
    }
    return 0;
}

以下是打印結(jié)果


三種獲取內(nèi)存大小的打印結(jié)果

總結(jié)

  • sizeof:計(jì)算類(lèi)型占用的內(nèi)存大小,其中可以放 基本數(shù)據(jù)類(lèi)型、對(duì)象、指針

    • 對(duì)于類(lèi)似于int這樣的基本數(shù)據(jù)而言,sizeof獲取的就是數(shù)據(jù)類(lèi)型占用的內(nèi)存大小,不同的數(shù)據(jù)類(lèi)型所占用的內(nèi)存大小是不一樣的

    • 而對(duì)于類(lèi)似于NSObject定義的實(shí)例對(duì)象而言,其對(duì)象類(lèi)型的本質(zhì)就是一個(gè)結(jié)構(gòu)體(即 struct objc_object)的指針,所以sizeof(objc)打印的是對(duì)象objc的指針大小,我們知道一個(gè)指針的內(nèi)存大小是8,所以sizeof(objc) 打印是 8。注意:這里的8字節(jié)與isa指針一點(diǎn)關(guān)系都沒(méi)有!!!)

    • 對(duì)于指針而言,sizeof打印的就是8,因?yàn)橐粋€(gè)指針的內(nèi)存大小是8,

  • class_getInstanceSize:計(jì)算對(duì)象實(shí)際占用的內(nèi)存大小,這個(gè)需要依據(jù)類(lèi)的屬性而變化,如果自定義類(lèi)沒(méi)有自定義屬性,僅僅只是繼承自NSObject,則類(lèi)的實(shí)例對(duì)象實(shí)際占用的內(nèi)存大小是8,可以簡(jiǎn)單理解為8字節(jié)對(duì)齊

  • malloc_size:計(jì)算對(duì)象實(shí)際分配的內(nèi)存大小,這個(gè)是由系統(tǒng)完成的,可以從上面的打印結(jié)果看出,實(shí)際分配的和實(shí)際占用的內(nèi)存大小并不相等,這個(gè)問(wèn)題可以通過(guò)iOS-底層原理 02:alloc & init & new 源碼分析中的16字節(jié)對(duì)齊算法來(lái)解釋這個(gè)問(wèn)題

結(jié)構(gòu)體內(nèi)存對(duì)齊

接下來(lái),我們首先定義兩個(gè)結(jié)構(gòu)體,分別計(jì)算他們的內(nèi)存大小,以此來(lái)引入今天的正題:內(nèi)存對(duì)齊原理

//1、定義兩個(gè)結(jié)構(gòu)體
struct Mystruct1{
    char a;     //1字節(jié)
    double b;   //8字節(jié)
    int c;      //4字節(jié)
    short d;    //2字節(jié)
}Mystruct1;

struct Mystruct2{
    double b;   //8字節(jié)
    int c;      //4字節(jié)
    short d;    //2字節(jié)
    char a;     //1字節(jié)
}Mystruct2;

//計(jì)算 結(jié)構(gòu)體占用的內(nèi)存大小
NSLog(@"%lu-%lu",sizeof(Mystruct1),sizeof(Mystruct2));

以下是輸出結(jié)果


image

從打印結(jié)果我們可以看出一個(gè)問(wèn)題,兩個(gè)結(jié)構(gòu)體乍一看,沒(méi)什么區(qū)別,其中定義的變量 和 變量類(lèi)型都是一致的,唯一的區(qū)別只是在于定義變量的順序不一致,那為什么他們做占用的內(nèi)存大小不相等呢?其實(shí)這就是iOS中的內(nèi)存字節(jié)對(duì)齊現(xiàn)象

內(nèi)存對(duì)齊規(guī)則

每個(gè)特定平臺(tái)上的編譯器都有自己的默認(rèn)“對(duì)齊系數(shù)”(也叫對(duì)齊模數(shù))。程序員可以通過(guò)預(yù)編譯命令#pragma pack(n),n=1,2,4,8,16來(lái)改變這一系數(shù),其中的n就是你要指定的“對(duì)齊系數(shù)”。在ios中,Xcode默認(rèn)為#pragma pack(8),即8字節(jié)對(duì)齊

一般內(nèi)存對(duì)齊的原則主要有3點(diǎn),可以回看iOS-底層原理 02:alloc & init & new 源碼分析中的說(shuō)明,

可以將內(nèi)存對(duì)齊原則可以理解為以下兩點(diǎn):

  • 【原則一】 數(shù)據(jù)成員的對(duì)齊規(guī)則可以理解為min(m, n) 的公式, 其中 m表示當(dāng)前成員的開(kāi)始位置, n表示當(dāng)前成員所需要的位數(shù)。如果滿足條件 m 整除 n (即 m % n == 0), nm 位置開(kāi)始存儲(chǔ), 反之繼續(xù)檢查 m+1 能否整除 n, 直到可以整除, 從而就確定了當(dāng)前成員的開(kāi)始位置。
  • 【原則二】數(shù)據(jù)成員為結(jié)構(gòu)體:當(dāng)結(jié)構(gòu)體嵌套了結(jié)構(gòu)體時(shí),作為數(shù)據(jù)成員的結(jié)構(gòu)體的自身長(zhǎng)度作為外部結(jié)構(gòu)體的最大成員的內(nèi)存大小,比如結(jié)構(gòu)體a嵌套結(jié)構(gòu)體b,b中有char、int、double等,則b的自身長(zhǎng)度為8
  • 【原則三】最后結(jié)構(gòu)體的內(nèi)存大小必須是結(jié)構(gòu)體中最大成員內(nèi)存大小的整數(shù)倍,不足的需要補(bǔ)齊。

驗(yàn)證對(duì)齊規(guī)則

下表是各種數(shù)據(jù)類(lèi)型在ios中的占用內(nèi)存大小,根據(jù)對(duì)應(yīng)類(lèi)型來(lái)計(jì)算結(jié)構(gòu)體中內(nèi)存大小


數(shù)據(jù)類(lèi)型對(duì)應(yīng)的字節(jié)數(shù)表格

我們可以通過(guò)下圖圖來(lái)說(shuō)明下為什么兩個(gè)結(jié)構(gòu)體MyStruct1 & MyStruct2的內(nèi)存大小打印不一致的情況,如圖所示

結(jié)構(gòu)體對(duì)應(yīng)的存儲(chǔ)情況

結(jié)構(gòu)體MyStruct1 內(nèi)存大小計(jì)算

根據(jù)內(nèi)存對(duì)齊規(guī)則計(jì)算MyStruct1的內(nèi)存大小,詳解過(guò)程如下:

  • 變量a:占1個(gè)字節(jié),從0開(kāi)始,此時(shí)min(0,1),即 0 存儲(chǔ) a
  • 變量b:占8個(gè)字節(jié),從1開(kāi)始,此時(shí)min(1,8),1不能整除8,繼續(xù)往后移動(dòng),知道min(8,8),從8開(kāi)始,即 8-15 存儲(chǔ) b
  • 變量c:占4個(gè)字節(jié),從16開(kāi)始,此時(shí)min(16,4),16可以整除4,即 16-19 存儲(chǔ) c
  • 變量d:占2個(gè)字節(jié),從20開(kāi)始,此時(shí)min(20, 2),20可以整除2,即20-21 存儲(chǔ) d

因此MyStruct1的需要的內(nèi)存大小為 15字節(jié),而MyStruct1中最大變量的字節(jié)數(shù)為8,所以 MyStruct1 實(shí)際的內(nèi)存大小必須是 8 的整數(shù)倍,18向上取整到24,主要是因?yàn)?4是8的整數(shù)倍,所以 sizeof(MyStruct1) 的結(jié)果是 24

結(jié)構(gòu)體MyStruct2 內(nèi)存大小計(jì)算

根據(jù)內(nèi)存對(duì)齊規(guī)則計(jì)算MyStruct2的內(nèi)存大小,詳解過(guò)程如下:

  • 變量b:占8個(gè)字節(jié),從0開(kāi)始,此時(shí)min(0,8),即 0-7 存儲(chǔ) b
  • 變量c:占4個(gè)字節(jié),從8開(kāi)始,此時(shí)min(8,4),8可以整除4,即 8-11 存儲(chǔ) c
  • 變量d:占2個(gè)字節(jié),從12開(kāi)始,此時(shí)min(12, 2),12可以整除2,即12-13 存儲(chǔ) d
  • 變量a:占1個(gè)字節(jié),從14開(kāi)始,此時(shí)min(14,1),即 14 存儲(chǔ) a

因此MyStruct2的需要的內(nèi)存大小為 15字節(jié),而MyStruct1中最大變量的字節(jié)數(shù)為8,所以 MyStruct2 實(shí)際的內(nèi)存大小必須是 8 的整數(shù)倍,15向上取整到16,主要是因?yàn)?6是8的整數(shù)倍,所以 sizeof(MyStruct2) 的結(jié)果是 16

結(jié)構(gòu)體嵌套結(jié)構(gòu)體

上面的兩個(gè)結(jié)構(gòu)體只是簡(jiǎn)單的定義數(shù)據(jù)成員,下面來(lái)一個(gè)比較復(fù)雜的,結(jié)構(gòu)體中嵌套結(jié)構(gòu)體的內(nèi)存大小計(jì)算情況

  • 首先定義一個(gè)結(jié)構(gòu)體MyStruct3,在MyStruct3中嵌套MyStruct2,如下所示
//1、結(jié)構(gòu)體嵌套結(jié)構(gòu)體
struct Mystruct3{
    double b;   //8字節(jié)
    int c;      //4字節(jié)
    short d;    //2字節(jié)
    char a;     //1字節(jié)
    struct Mystruct2 str; 
}Mystruct3;

//2、打印 Mystruct3 的內(nèi)存大小
NSLog(@"Mystruct3內(nèi)存大小:%lu", sizeof(Mystruct3));
NSLog(@"Mystruct3中結(jié)構(gòu)體成員內(nèi)存大小:%lu", sizeof(Mystruct3.str));

打印 的結(jié)果如下所示


image
  • 分析Mystruct3的內(nèi)存計(jì)算
    根據(jù)內(nèi)存對(duì)齊規(guī)則,來(lái)一步一步分析Mystruct3內(nèi)存大小的計(jì)算過(guò)程

    • 變量b:占8個(gè)字節(jié),從0開(kāi)始,此時(shí)min(0,8),即 0-7 存儲(chǔ) b
    • 變量c:占4個(gè)字節(jié),從8開(kāi)始,此時(shí)min(8,4),8可以整除4,即 8-11 存儲(chǔ) c
    • 變量d:占2個(gè)字節(jié),從12開(kāi)始,此時(shí)min(12, 2),20可以整除2,即12-13 存儲(chǔ) d
    • 變量a:占1個(gè)字節(jié),從14開(kāi)始,此時(shí)min(14,1),即 14 存儲(chǔ) a
    • 結(jié)構(gòu)體成員str:str是一個(gè)結(jié)構(gòu)體,根據(jù)內(nèi)存對(duì)齊原則二結(jié)構(gòu)體成員要從其內(nèi)部最大成員大小的整數(shù)倍開(kāi)始存儲(chǔ),而MyStruct2最大的成員大小為8,所以str要從8的整數(shù)倍開(kāi)始,當(dāng)前是從15開(kāi)始,所以不符合要求,需要往后移動(dòng)到16,16是8的整數(shù)倍,符合內(nèi)存對(duì)齊原則,所以 16-31 存儲(chǔ) str

因此MyStruct3的需要的內(nèi)存大小為 32字節(jié),而MyStruct3中最大變量為str, 其最大成員內(nèi)存字節(jié)數(shù)為8,根據(jù)內(nèi)存對(duì)齊原則,所以 MyStruct3 實(shí)際的內(nèi)存大小必須是 8 的整數(shù)倍,32正好是8的整數(shù)倍,所以 sizeof(MyStruct3) 的結(jié)果是 32

其內(nèi)存存儲(chǔ)情況如下圖所示


結(jié)構(gòu)體嵌套結(jié)構(gòu)體的內(nèi)存存儲(chǔ)情況

二次驗(yàn)證

為了保險(xiǎn)起見(jiàn),我們?cè)俣x一個(gè)結(jié)構(gòu)體,來(lái)驗(yàn)證我們結(jié)構(gòu)體嵌套的內(nèi)存大小計(jì)算說(shuō)明

struct Mystruct4{
    int a;              //4字節(jié) min(0,4)--- (0,1,2,3)
    struct Mystruct5{   //從4開(kāi)始,存儲(chǔ)開(kāi)始位置必須是最大的整數(shù)倍(最大成員為8),min(4,8)不符合 4,5,6,7,8 -- min(8,8)滿足,從8開(kāi)始存儲(chǔ)
        double b;       //8字節(jié) min(8,8)  --- (8,9,10,11,12,13,14,15)
        short c;         //1字節(jié),從16開(kāi)始,min(16,1) -- (16,17)
    }Mystruct5;
}Mystruct4;

分析如下

  • 變量a:占4字節(jié),從0開(kāi)始,min(0,4),即 0-3存儲(chǔ)a
  • 結(jié)構(gòu)體Mystruct5:從4開(kāi)始,根據(jù)內(nèi)存對(duì)齊原則二,即存儲(chǔ)開(kāi)始位置必須是最大的整數(shù)倍(最大成員為8),min(4,8)不能整除,繼續(xù)往后移動(dòng),直到8, min(8,8)滿足,從8開(kāi)始存儲(chǔ)結(jié)構(gòu)體Mystruct5的變量
    • 變量b:占8字節(jié),從8開(kāi)始,min(8,8),可以整除,即 8-15存儲(chǔ)b
    • 變量c:占2字節(jié),從16開(kāi)始,min(16,2),可以整除,即16-17存儲(chǔ)c

因此Mystruct4中需要的內(nèi)存大小是 18字節(jié),根據(jù)內(nèi)存對(duì)其原則二,Mystruct4實(shí)際的內(nèi)存大小必須是Mystruct5最大成員b的整數(shù)倍,即必須是8的整數(shù)倍,所以sizeof(Mystruct4) 的結(jié)果是 24

以下是運(yùn)行結(jié)果的打印,以此來(lái)印證24這個(gè)內(nèi)存大小

內(nèi)存優(yōu)化(屬性重排)

MyStruct1 通過(guò)內(nèi)存字節(jié)對(duì)齊原則,增加了9個(gè)字節(jié),而MyStruct2通過(guò)內(nèi)存字節(jié)對(duì)齊原則,通過(guò)4+2+1的組合,只需要補(bǔ)齊一個(gè)字節(jié)即可滿足字節(jié)對(duì)齊規(guī)則,這里得出一個(gè)結(jié)論結(jié)構(gòu)體內(nèi)存大小與結(jié)構(gòu)體成員內(nèi)存大小的順序有關(guān)

  • 如果是結(jié)構(gòu)體中數(shù)據(jù)成員是根據(jù)內(nèi)存從小到大的順序定義的,根據(jù)內(nèi)存對(duì)齊規(guī)則來(lái)計(jì)算結(jié)構(gòu)體內(nèi)存大小,需要增加有較大的內(nèi)存padding即內(nèi)存占位符,才能滿足內(nèi)存對(duì)齊規(guī)則,比較浪費(fèi)內(nèi)存

  • 如果是結(jié)構(gòu)體中數(shù)據(jù)成員是根據(jù)內(nèi)存從大到小的順序定義的,根據(jù)內(nèi)存對(duì)齊規(guī)則來(lái)計(jì)算結(jié)構(gòu)體內(nèi)存大小,我們只需要補(bǔ)齊少量?jī)?nèi)存padding即可滿足堆存對(duì)齊規(guī)則,這種方式就是蘋(píng)果中采用的,利用空間換時(shí)間,將類(lèi)中的屬性進(jìn)行重排,來(lái)達(dá)到優(yōu)化內(nèi)存的目的

以下面這個(gè)例子來(lái)進(jìn)行說(shuō)明 蘋(píng)果中屬性重排,即內(nèi)存優(yōu)化

  • 定義一個(gè)自定義CJLPerson類(lèi),并定義幾個(gè)屬性,
@interface CJLPerson : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
// @property (nonatomic, copy) NSString *hobby;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;

@property (nonatomic) char c1;
@property (nonatomic) char c2;
@end

@implementation CJLPerson

@end
  • 在main中創(chuàng)建CJLPerson的實(shí)例對(duì)象,并對(duì)其中的幾個(gè)屬性賦值
int main(int argc, char * argv[]) {
    @autoreleasepool {
        CJLPerson *person = [CJLPerson alloc];
        person.name      = @"CJL";
        person.nickName  = @"C";
        person.age       = 18;
        person.c1        = 'a';
        person.c2        = 'b';

        NSLog(@"%@",person);
    }
    return 0;
}
  • 斷點(diǎn)調(diào)試person,根據(jù)CJLPerson對(duì)象地址,查找出屬性的值
    • 通過(guò)地址找出 name & nickName

      image

    • 當(dāng)我們向通過(guò)0x0000001200006261地址找出age等數(shù)據(jù)時(shí),發(fā)現(xiàn)是亂碼,這里無(wú)法找出值的原因是蘋(píng)果中針對(duì)age、c1、c2屬性的內(nèi)存進(jìn)行了重排,因?yàn)閍ge類(lèi)型占4個(gè)字節(jié),c1和c2類(lèi)型char分別占1個(gè)字節(jié),通過(guò)4+1+1的方式,按照8字節(jié)對(duì)齊,不足補(bǔ)齊的方式存儲(chǔ)在同一塊內(nèi)存中,

      • age的讀取通過(guò)0x00000012
      • c1的讀取通過(guò)0x61(a的ASCII碼是97)
      • c2的讀取通過(guò)0x62(b的ASCII碼是98)
        image

下圖是CJLPerson的內(nèi)存分布情況


CJLPerson內(nèi)存存儲(chǔ)情況

注意:
1、char類(lèi)型的數(shù)據(jù)讀取出來(lái)是以ASCII碼的形式顯示
2、圖片中地址為0x0000000000000000,表示person中還有屬性未賦值

總結(jié)

所以,這里可以總結(jié)下蘋(píng)果中的內(nèi)存對(duì)齊思想:

  • 大部分的內(nèi)存都是通過(guò)固定的內(nèi)存塊進(jìn)行讀取,
  • 盡管我們?cè)趦?nèi)存中采用了內(nèi)存對(duì)齊的方式,但并不是所有的內(nèi)存都可以進(jìn)行浪費(fèi)的,蘋(píng)果會(huì)自動(dòng)對(duì)屬性進(jìn)行重排,以此來(lái)優(yōu)化內(nèi)存

字節(jié)對(duì)齊到底采用多少字節(jié)對(duì)齊?

到目前為止,我們?cè)谇拔募忍岬搅?字節(jié)對(duì)齊,也提及了16字節(jié)對(duì)齊,那我們到底采用哪種字節(jié)對(duì)齊呢?

我們可以通過(guò)objc4中class_getInstanceSize的源碼來(lái)進(jìn)行分析

/** 
 * Returns the size of instances of a class.
 * 
 * @param cls A class object.
 * 
 * @return The size in bytes of instances of the class \e cls, or \c 0 if \e cls is \c Nil.
 */
OBJC_EXPORT size_t
class_getInstanceSize(Class _Nullable cls) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

??

size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}

??

// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
    return word_align(unalignedInstanceSize());
}

??

static inline uint32_t word_align(uint32_t x) {
    //x+7 & (~7) --> 8字節(jié)對(duì)齊
    return (x + WORD_MASK) & ~WORD_MASK;
}


//其中 WORD_MASK 為
#   define WORD_MASK 7UL

通過(guò)源碼可知:

  • 對(duì)于一個(gè)對(duì)象來(lái)說(shuō),其真正的對(duì)齊方式8字節(jié)對(duì)齊,8字節(jié)對(duì)齊已經(jīng)足夠滿足對(duì)象的需求了
  • apple系統(tǒng)為了防止一切的容錯(cuò),采用的是16字節(jié)對(duì)齊的內(nèi)存,主要是因?yàn)椴捎?字節(jié)對(duì)齊時(shí),兩個(gè)對(duì)象的內(nèi)存會(huì)緊挨著,顯得比較緊湊,而16字節(jié)比較寬松,利于蘋(píng)果以后的擴(kuò)展。

總結(jié)
綜合前文提及的獲取內(nèi)存大小的方式

  • class_getInstanceSize:是采用8字節(jié)對(duì)齊,參照的對(duì)象的屬性?xún)?nèi)存大小
  • malloc_size:采用16字節(jié)對(duì)齊,參照的整個(gè)對(duì)象的內(nèi)存大小,對(duì)象實(shí)際分配的內(nèi)存大小必須是16的整數(shù)倍

內(nèi)存對(duì)齊算法

目前已知的16字節(jié)內(nèi)存對(duì)齊算法有兩種

  • alloc源碼分析中的align16
  • malloc源碼分析中的segregated_size_to_fit

align16: 16字節(jié)對(duì)齊算法

這個(gè)算法的思想已經(jīng)在iOS-底層原理 02:alloc & init & new 源碼分析中有所提及

static inline size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15);
}

segregated_size_to_fit: 16字節(jié)對(duì)齊算法

#define SHIFT_NANO_QUANTUM      4
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM)   // 16

static MALLOC_INLINE size_t
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
    size_t k, slot_bytes;

    if (0 == size) {
        size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
    }
    k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
    slot_bytes = k << SHIFT_NANO_QUANTUM;                           // multiply by power of two quanta size
    *pKey = k - 1;                                                  // Zero-based!

    return slot_bytes;
}

算法原理:k + 15 >> 4 << 4 ,其中 右移4 + 左移4相當(dāng)于將后4位抹零,跟 k/16 * 16一樣 ,是16字節(jié)對(duì)齊算法,小于16就成0了

以 k = 2為例,如下圖所示


libmalloc中16字節(jié)對(duì)齊算法原理
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。