iOS ------ 对象的本质

一,OC对象本质,用clang编译main.m

OC对象结构都是通过基础的C/C++结构体实现的,我们通过创建OC文件及对象,将OC对象转化为C++文件来探寻OC对象的本质。

代码:


@interface HTPerson : NSObject
@property(nonatomic,strong)NSString *name;
@end

@implementation HTPerson

@end
int main(int argc, const char * argv[]) {
   @autoreleasepool {
       HTPerson *person = [[HTPerson alloc]init];
      
       NSLog(@"Hello, World!");
   }
   return 0;
}

通过命令行将OC的main文件转化为C++文件

我们通过命令行将OC的mian.m文件转化为c++文件。

复制代码clang -rewrite-objc main.m -o main.cpp // 这种方式没有指定架构例如arm64架构 其中cpp代表(c plus plus)
生成 main.cpp

我们可以指定架构模式的命令行,使用xcode工具 xcrun

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 
生成 main-arm64.cpp 
#ifndef _REWRITER_typedef_HTPerson
#define _REWRITER_typedef_HTPerson
typedef struct objc_object HTPerson;
typedef struct {} _objc_exc_HTPerson;
#endif

extern "C" unsigned long OBJC_IVAR_$_HTPerson$_name;
struct HTPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString *_name;
};

// @property (nonatomic, strong) NSString* name;
/* @end */

// @implementation HTPerson

static NSString * _I_HTPerson_name(HTPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_HTPerson$_name)); }
static void _I_HTPerson_setName_(HTPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_HTPerson$_name)) = name; }
// @end
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        HTPerson* person = ((HTPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((HTPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("HTPerson"), sel_registerName("alloc")), sel_registerName("init"));
    }
    return 0;
}

可以观察到HTPerson类的本质就是结构体(struct

struct HTPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString *_name;
};

struct NSObject_IMPL NSObject_IVARS是继承于父类NSObject的实例变量。这种方法模拟了类继承的行为,确保HTPerson对象不仅拥有自己的实例变量_name,还继承了NSOBject的所有实例变量。

typedef struct objc_class *Class;
struct NSObject_IMPL {
	Class isa;
};

我们发现class其实就是一个指针,isa是一个指向类对象的指针,每个OC对象都有一个isa指针,指向它的类

extern "C" unsigned long OBJC_IVAR_$_HTPerson$_name;

这声明了一个全局变量用于_name实例变量在HTPerson对象中的偏移量。编译器使用这个变量来访问对象的实例变量。

下面是HTPerson属性的set,get方法的底层实现

static NSString * _I_HTPerson_name(HTPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_HTPerson$_name)); }
static void _I_HTPerson_setName_(HTPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_HTPerson$_name)) = name; }

在set、get方法的底层实现中 OBJC_IVAR_$_SMPerson$_name 就是属性对应的内存地址的偏移量, 系统通过每个属性的偏移量,来实现对其赋值和取值。

typedef struct objc_object HTPerson;

objc_object 结构体类型定义为HTPerson类型

  • objc_object:包含一个指向类的指针isa的结构体,所有OC对象都继承自它。
typedef struct objc_class *Class;
struct objc_object {
    Class _Nonnull isa __attribute__((deprecated));
};
  • HTPerson:这行代码定义了一个新的类型名 HTPerson,它是 struct objc_object 类型的别名。

二,OC对象在内存中的布局

以NSObject为例,点击NSObject进入发现NSObject的内部实现

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
@end

转化为c语言函数就是一个结构体

struct NSObject_IMPL {
    Class isa;
};

这个结构体只有一个成员isa指针,而指针在64位架构中占8个字节,也就是说一个NSObject对象所暂用的内存是8个字节。对于OC对象还有一些方法,这些方法也是占用内存的,但这些方法所占用的空间并不在NSObject对象中

探寻NSObject对象在内存中如何体现的

NSObject *objc = [[NSObject alloc] init];

上述一段代码中系统为NSObject对象分配8个字节的内存空间,用来存放一个成员isa指针。那么isa指针这个变量的地址就是结构体的起始地址,也就是NSObjcet对象的地址。
假设isa的地址为0x100400110,那么上述代码分配存储空间给NSObject对象,然后将存储空间的地址赋值给objc指针。objc存储的就是isa的地址。objc指向内存中NSObject对象地址,即指向内存中的结构体,也就是isa的位置。

自定义类的内部实现

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface HTPerson : NSObject
@property (nonatomic, strong) NSString* name;
@property (nonatomic) int number;
@property (nonatomic, strong) NSString* phone;
@end

@implementation HTPerson

@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        HTPerson* person = [[HTPerson alloc] init];
        person.name = @"name";
        person.number = 1;
        person.phone = @"123";
        NSLog(@"%zd", class_getInstanceSize([HTPerson class]));
        
    }
    return 0;
}

struct HTPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	int _number;
	NSString *_name;
	NSString *_phone;
};

NSObject_IMPL内部其实就是Class isa 那么我们假设 struct NSObject_IMPL NSObject_IVARS; 等价于 Class isa;
所以可以转化为

struct HTPerson_IMPL {
	Class *isa;
	int _number;
	NSString *_name;
	NSString *_phone;
};

因此此结构体占用多少存储空间,对象就占用多少存储空间。输出结果为32.

现在我们来计算总的内存大小,考虑内存对齐:

  • NSObject_IVARS(假设它包含一个指针):8字节
  • _number:4字节
    对齐填充:4字节(因为接下来是指针,按8字节对齐)
  • _name:8字节
  • _phone:8字节

所以OC对象的内存布局也遵守结构体的内存对齐规则。

  1. 前面的地址必须是后面的地址正数倍,不是就补齐。
  2. 整个Struct的地址必须是最大字节的整数倍。

复杂的继承关系

/* Person */
@interface Person : NSObject
{
    int _age;
}
@end

@implementation Person
@end

/* Student */
@interface Student : Person
{
    int _no;
}
@end

@implementation Student
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSLog(@"%zd  %zd",
              class_getInstanceSize([Person class]),
              class_getInstanceSize([Student class])
              );
    }
    return 0;
}

画出内存图例
在这里插入图片描述

我们发现只要继承自NSObject的对象,那么底层结构体内一定有一个isa指针。
上面的输出结果为16,16。

person对象只使用了12个字节,但是因为内存对齐的原因,使person对象也占用16个字节。对于student对象,包含了person对象的结构体实现,和一个int类型的_no成员变量,同样isa指针8个字节,_age成员变量4个字节,_no成员变量4个字节,根据内存对齐原则student对象占据的内存空间也是16个字节。

类的本质

OC的类信息存放在哪里? OC对象主要可以分为三种

  1. instance对象(实例对象)
  2. class对象(类对象)
  3. meta-class对象(元类对象)

instance对象就是通过类alloc出来的对象,每次调用alloc都会产生新的instance对象。

NSObjcet *object1 = [[NSObjcet alloc] init];
NSObjcet *object2 = [[NSObjcet alloc] init];

object1object2都是NSObjectinstace对象(实例对象),但他们是不同的两个对象,并且分别占据着两块不同的内存。 instance对象在内存中存储的信息包括isa指针其他成员变量

在这里插入图片描述

class对象 我们通过class方法或runtime方法得到一个class对象。class对象也就是类对象

Class objectClass1 = [object1 class];//获取类对象
Class objectClass2 = [NSObject class];//获取元类

// runtime
Class objectClass4 = object_getClass(object1);

每一个类在内存中有且只有一个对象
class对象在内存中存储的信息主要包括

  1. isa指针
  2. superclass指针
  3. 类的属性信息(@property),类的成员变量信息(ivar)
  4. 类的对象方法信息(instance method),类的协议信息(protocol)

instance对象中的成员变量是具体对象的属性,每个实例都有自己的一份副本,是成员变量具体的值。创建实例对象的时候要为成员变量赋值。而类对象中的类的成员变量信息与类本身关联,而不是与类的每个实例关联。

meta_class对象 每个类在内存中有且只有一个meta-class对象。在内存中存储的信息主要包括

  1. isa指针
  2. superclass指针
  3. 类的类方法的信息(class method)

meta-class对象class对象的内存结构是一样的,所以meta-class中也有类的属性信息,类的对象方法信息等成员变量,但是其中的值可能是空的

对象的isa指针的指向

  • 当对象调用实例方法的时候,实例方法信息是存储在class类对象中的,instance的isa指向class,通过instance的isa找到class,最后找到对象方法的实现进行调用。
  • 当类对象调用类方法的时候,类方法是存储在meta-class元类对象中的。class类对象的isa指针就指向元类对象,通过class的isa找到meta-class,最后找到类方法的实现进行调用

在这里插入图片描述

  • 当对象调用其父类对象方法的时候,此时就需要使用到class类对象superclass指针。当Student的instance对象要调用Person的对象方法时,会先通过isa找到Student的class,然后通过superclass找到Person的class,最后找到对象方法的实现进行调用。
  • 当类对象调用父类的类方法时,当Student的class要调用Person的类方法时,会先通过isa找到Student的meta-class,然后通过superclass找到Person的meta-class,最后找到类方法的实现进行调用

在这里插入图片描述

不管是类对象还是元类对象,类型都是Classclassmete-class的底层都是objc_class结构体的指针,内存中就是结构体

struct objc_class : objc_object {
    ...省略无关代码
    // Class ISA;  //ISA(从objc_object继承过来的)指向元类
    Class superclass;  //指向其父类
    cache_t cache;  //缓存

    class_data_bits_t bits;  //类的数据

    class_rw_t *data() const {
      return bits.data();
    }
    void setData(class_rw_t *newData) {
      bits.setData(newData);
    }

    ...省略无关代码
}  

这个结构体继承 objc_object 并且结构体内有一些函数,因为这是c++结构体,在c上做了扩展,因此结构体中可以包含函数。

前面讲到bjc_object中有一个isa指针,那么objc_class继承objc_object,也就同样拥有一个isa指针。

类中存储的类的成员变量信息,实例方法,属性名等这些信息在哪里呢。

    class_data_bits_t bits;  //类的数据

    class_rw_t *data() const {
      return bits.data();
    }
    void setData(class_rw_t *newData) {
      bits.setData(newData);
    }

obj_class中有class_rw_t类型的bits.data的读写
我们来到class_rw_t中,class_rw_t 结构体是 Objective-C 运行时中用于描述类的可读写数据的结构体。它包含了类的动态数据,可以在运行时修改。这些数据包括标志、方法列表、属性列表、协议列表等。

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;

private:
    using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t, class_rw_ext_t, PTRAUTH_STR("class_ro_t"), PTRAUTH_STR("class_rw_ext_t")>;

    const ro_or_rw_ext_t get_ro_or_rwe() const {
        return ro_or_rw_ext_t{ro_or_rw_ext};
    }

    void set_ro_or_rwe(const class_ro_t *ro) {
        ro_or_rw_ext_t{ro, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_relaxed);
    }

    void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
        // the release barrier is so that the class_rw_ext_t::ro initialization
        // is visible to lockless readers
        rwe->ro = ro;
        ro_or_rw_ext_t{rwe, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_release);
    }

    class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);

public:
    void setFlags(uint32_t set)
    {
        __c11_atomic_fetch_or((_Atomic(uint32_t) *)&flags, set, __ATOMIC_RELAXED);
    }

    void clearFlags(uint32_t clear)
    {
        __c11_atomic_fetch_and((_Atomic(uint32_t) *)&flags, ~clear, __ATOMIC_RELAXED);
    }

    // set and clear must not overlap
    void changeFlags(uint32_t set, uint32_t clear)
    {
        ASSERT((set & clear) == 0);

        uint32_t oldf, newf;
        do {
            oldf = flags;
            newf = (oldf | set) & ~clear;
        } while (!CompareAndSwap(oldf, newf, &flags));
    }

    class_rw_ext_t *ext() const {
        return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
    }

    class_rw_ext_t *extAllocIfNeeded() {
        auto v = get_ro_or_rwe();
        if (fastpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
        } else {
            return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
        }
    }

    class_rw_ext_t *deepCopy(const class_ro_t *ro) {
        return extAlloc(ro, true);
    }

    const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        if (slowpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
        }
        return v.get<const class_ro_t *>(&ro_or_rw_ext);
    }

    void set_ro(const class_ro_t *ro) {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
        } else {
            set_ro_or_rwe(ro);
        }
    }

    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods};
        }
    }//方法列表

    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
        }
    }//属性列表

    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
        }
    }//协议列表
}

而成员变量信息则是存储在class_ro_t内部中的,我们来到class_ro_t内查看。

class_ro_t 结构体是 Objective-C 运行时中用于描述类的只读数据的重要部分,包含了类的方法列表、属性列表、协议列表等信息。通过 ro_or_rw_ext 成员,class_rw_t 可以指向只读数据(class_ro_t)或读写扩展数据(class_rw_ext_t),并通过成员函数来访问和修改这些数据。

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;//实例对象大小
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout;
    const char * name;//类名
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;//成员变量
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
    // This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
    _objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
    _objc_swiftMetadataInitializer swiftMetadataInitializer() const {
        if (flags & RO_HAS_SWIFT_INITIALIZER) {
            return _swiftMetadataInitializer_NEVER_USE[0];
        } else {
            return nil;
        }
    }
    method_list_t *baseMethods() const {
        return baseMethodList;
    }
    class_ro_t *duplicate() const {
        if (flags & RO_HAS_SWIFT_INITIALIZER) {
            size_t size = sizeof(*this) + sizeof(_swiftMetadataInitializer_NEVER_USE[0]);
            class_ro_t *ro = (class_ro_t *)memdup(this, size);
            ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];
            return ro;
        } else {
            size_t size = sizeof(*this);
            class_ro_t *ro = (class_ro_t *)memdup(this, size);
            return ro;
        }
    }
};

总结如图:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/700091.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

axure9设置组件自适应浏览器大小

问题&#xff1a;预览时不展示下方的滚动条 方法一&#xff1a;转化为动态面板 1.在页面上创建一个矩形 2.右键-转化为动态面板 3.双击进入动态面板设置 4.设置动态面板矩形的颜色 5.删除原来的矩形 6.关闭动态面板&#xff0c;点击预览 7.此时可以发现底部没有滚动条了 方法…

面试题:如何避免索引失效?

(1) 范围条件查询 (2) 不要在索引上使用函数运算, 否则索引也会失效. 比如在索引上使用切割函数, 就会使索引失效. (3) 字符串不加引号, 造成索引失效. (4) 尽量使用索引覆盖, 避免 select *, 这样能提高查询效率. 如果索引列完全包含查询列, 那么查询的时候把要查的列写出来…

插入删除单链表指定结点-偷天换日法

王道说下面的代码有BUG&#xff0c;比如当删除的结点p在最后一个元素时&#xff0c;p->nextNULL; So *q NULL; q->data就是错误的&#xff0c;我认为加个判断就行 加个判断即可 /*看着是删除q了&#xff0c;从结果上看就是把p删除了 偷天换日法*/ bool DeleteNode(LNod…

【源码】二开版微盘交易系统/贵金属交易平台/微交易系统

二开版微盘交易系统/贵金属交易平台/微交易系统 一套二开前端UI得贵金属微交易系统&#xff0c;前端产品后台可任意更换 此系统框架不是以往的至尊的框架&#xff0c;系统完美运行&#xff0c;K线采用nodejs方式运行 K线结算都正常&#xff0c;附带教程 资源来源:https://www.…

小迪安全代码语言回溯

java安全 第一个就是文件上传&#xff0c;可以通过../上传到上一级目录&#xff0c;以及别的目录&#xff0c;避免本目录不可以执行 jw令牌窃取 令牌由三部分组成&#xff0c;以.号分割&#xff0c;在java的程序看到cookie是三个奇怪的字符串以.号分开&#xff0c;可以确定是…

matlab-2-simulink-小白教程-如何绘制电路图进行电路仿真

以上述电路图为例&#xff1a;包含D触发器&#xff0c;时钟CLK,与非门 一、启动simulink的三种方式 方式1 在MATLAB的命令行窗口输入“Simulink”命令。 方式2 在MATLAB主窗口的“主页”选项卡中&#xff0c;单击“SIMULINK”命令组中的Simulink命令按钮。 方式3 从MATLAB…

MySQL-----JOIN语句之内连接

在我们使用MySQL进行项目的开发过程中&#xff0c;仅仅对一张表进行操作是远远不够的&#xff0c;真正的应用中经常需要从多个数据表中读取数据。我们在使用的过程中&#xff0c;尝尝通过 SELECT, UPDATE 和 DELETE 语句配合使用 JOIN 来联合多表查询。 JOIN 分类 INNER JOIN…

【Text2SQL 论文】CHESS:利用上下文来合成 SQL 的 pipeline

文章目录 一、论文速读二、CHESS pipeline2.1 Entity and Context Retrieval2.2 Schema Selection2.3 Query Generation 三、预处理四、实验五、总结讨论 一、论文速读 本文提出了一个 pipeline 框架——CHESS——来解决应用于复杂的真实数据库场景下的 Text2SQL 问题。 在现…

AC/DC电源模块的原理、特点以及其在实际应用中的重要性

BOSHIDA AC/DC电源模块的原理、特点以及其在实际应用中的重要性 AC/DC电源模块是一种用于将交流电转换为直流电的设备&#xff0c;广泛应用于各种电子设备中。这种电源模块可以有效地将电力从电网中提取出来&#xff0c;并将其转换为稳定的直流电源&#xff0c;供给各种不同功…

有趣的数学 数值方法简述

数值方法简述 令许多纯数学家烦恼的是&#xff0c;并非所有问题都能通过解析方法解决&#xff0c;也就是说&#xff0c;不能通过使用已知规则和逻辑来获得精确解的方法。 这时就需要使用数值方法。 数值方法将近似解&#xff0c;或者在最坏的情况下&#xff0c;将解限制在某个范…

Sketch文件轻松转换为PSD的简便方法

由于Sketch只支持在Mac上使用&#xff0c;当设计师使用Sketch完成设计草案&#xff0c;需要与使用Windows的同事连接设计项目时&#xff0c;会遇到同事无法打开或在Photoshop中查看和编辑的情况&#xff0c;这真的很尴尬。别担心&#xff01;在本文中&#xff0c;我们将分享Ske…

笨蛋学算法之LeetCodeHot100_1_两数之和(Java)

package com.lsy.leetcodehot100;public class _Hot1_两数之和 {//自写方法public static int[] twoSum1(int[] nums, int target) {//定义存放返回变量的数组int[] arr new int[2];//遍历整个数组for (int i 0; i < nums.length; i) {//从第二个数开始相加判断for (int j…

mysql和redis的双写一致性问题

一&#xff0c;使用方案 在使用redis作为缓存的场景下&#xff0c;我们一般使用流程如下 二&#xff0c;更新数据场景 我们此时修改个某条数据&#xff0c;如何保证mysql数据库和redis缓存中的数据一致呢&#xff1f; 按照常规思路有四种办法&#xff0c;1.先更新mysql数据&a…

Nginx配置详细解释:(5)rewrite重写功能

rewrite重写功能&#xff0c;在编译安装时需要有相应的模块&#xff0c;ngx_http_rewritte_module模块指令中&#xff0c;有if指令&#xff0c;return,set,break等指令。 1.ngx_http_rewrite_module模块指令 1.if指令 if指令在nginx配置中&#xff0c;用于条件判断&#xff…

2024年山西水处理技术设备展览会11月8日召开

2024中国&#xff08;山西&#xff09;国际水务科技博览会 暨水处理技术设备与泵管阀展览会 时间&#xff1a;2024年11月8-10日 地点&#xff1a;山西潇河国际会展中心 推动城镇水务工作高质量发展&#xff0c;围绕解决水生态、水安全、水体黑臭、内涝积水等人民群众最关…

pikachu靶场通关全流程

目录 暴力破解&#xff1a; 1.基于表单的暴力破解&#xff1a; 2.验证码绕过(on server)&#xff1a; 3.验证码绕过(on client)&#xff1a; token防爆破&#xff1a; XSS&#xff1a; 1.反射型xss(get)&#xff1a; 2.反射性xss(post)&#xff1a; 3.存储型xss&#…

解决:RuntimeError: “slow_conv2d_cpu“ not implemented for ‘Half‘的方法之一

1. 问题描述 今天跑实验的时候&#xff0c;代码报错&#xff1a; RuntimeError: "slow_conv2d_cpu" not implemented for Half 感觉有点莫名奇妙&#xff0c;经检索&#xff0c;发现将fp16改为fp32可以解决我的问题&#xff0c;但是运行速度太慢了。后来发现&…

如何使用免费的 Instant Data Scraper快速抓取网页数据

Instant Data Scraper 是一款非常简单易用的网页数据爬虫工具&#xff0c;你不需要任何代码知识&#xff0c;只需要点几下鼠标&#xff0c;就可以把你想要的数据下载到表格里面。以下是详细的使用步骤&#xff1a; 第一步&#xff1a;安装 Instant Data Scraper 打开谷歌浏览…

ARM32开发--PWM与通用定时器

知不足而奋进望远山而前行 目录 文章目录 前言 学习目标 学习内容 PWM pwm原理 需求 开发流程 初始化PWM PWM占空比控制 main函数修改duty 输出通道 关心的内容 重要的关键词 周期 分频 占空比 总结 前言 在微控制器开发中&#xff0c;理解和掌握PWM&#x…

美团强势领涨恒指,港股即将迎来触底反弹?

恒指早间低开低走&#xff0c;持续低位徘徊&#xff0c;一度试探万八关口&#xff0c;最低见17994点&#xff0c;市场情绪表现疲弱&#xff0c;大型科技股普遍走低&#xff0c;但主要指数午后回升&#xff0c;恒生科技指数率先转涨&#xff0c;美团(3690.HK)涨超4%领涨成分股&a…