2016 - 2024

感恩一路有你

java代码结果怎么一键对齐 Java和C# 最大的不同是什么?

浏览量:3263 时间:2023-04-28 23:13:22 作者:采采

Java和C# 最大的不同是什么?

我觉得除了语法,最重要的是对底层的掌控能力不同。

虽然C#一开始借鉴了Java,但其目的根本不是为了构建一个更好的Java,而是为了构建一个更好的C,游戏引擎更喜欢C#就是这个原因。

例如,在C#中可以做什么:

上面的代码会输出10,为什么?因为数组的长度。NET存储在数组第一个元素之前的8字节内存中。如果您随后输出*(long *)p-2),您将直接获得该对象的TypeHandle地址:

然后拿着这个指针,就可以访问对象的MethodTable了。

此外,您可以在堆栈上手动分配空间:

然后,您希望绕过GC,直接手动分配堆内存:

以上调用相当于C语言中你调用的malloc,此外还有AllocAligned,Realloc,AllocZeroed等等,可以直接控制内存对齐。

接下来,您希望创建一个具有显式内存布局的结构Foo:

那么你就成功模拟了C的一个并集,之所以会有上面的输出,是因为单精度浮点数1的二进制表示是0x 01111111000000000000000000000000,在小终端模式存储后占用了4个字节,分别是0x00000000000、0x0000000000、0x 000000000。

此外,您可以直接从内存数据构造对象,而无需任何复制开销:

甚至像这样:

从堆内存中创建自然很好:

再比如,此时你有一个用C写的库,里面有这样一段代码:

然后我们编写下面的C#代码:

上面的代码做了什么?我们把C#的函数指针传入C代码,然后在C端调用C#函数生成一个字符串wwwww,然后把字符串返回给C#端。它不 使用委托而不是函数指针并不重要,因为函数指针在。网。

即使我们没有。;我不想要。NET要导入foo.dll,而我们想自己决定动态库的生命周期,我们也可以写:

以上都不是特定于Windows和导入的。所以还有。Linux和macOS上的dylib是完全不可能的。

此外,我们有一些数据,我们想计算,但我们想使用SIMD进行处理,所以我们只需要写:

您可以看到在X86平台上生成了什么代码:

平台判断的分支会被JIT自动淘汰。但事实上,除了手动编写SIMD,代码,前两个分支可以不写,只留:

因为在这个阶段,当循环边界条件是向量长度时,。NET会自动为我们做定向量化,扩展循环。

然后继续,我们还有ref,in和out来传递引用。

假设我们有一个大的struct,为了避免传递时的复制,我们可以直接使用in进行只读引用传递:

对于小型结构,为。NET有特殊的优化来帮助我们完全消除内存分配,并将结构完全放在寄存器中,如下面的代码:

上面的代码GetDistance被认为是一个热路径,所以我添加了它来指示JIT有保证地内联这个函数,最后生成了下面的代码进行测试:

整个过程没有访问内存的指令,效率非常高。

我们也可以借用ref的引用语义来做就地更新:

它甚至可以用于指针和手动分配内存:

与Java不同,C#中的泛型真正专门化了所有的类型参数(虽然运行时分布用于引用类型的共享实现),这意味着性能可以得到最大程度的保证,对应的类型根据类型参数的大小有专门的内存布局。还是上面的点例子,我们将下面的数据int替换为泛型参数t,并对值类型number进行泛型约束:

无论是Test1还是Test2,生成的代码都很优秀,不仅没有打包和解包,而且没有访问操作:

然后,我们有时为了高性能想暂时中止GC恢复,就一句简单的话:

如果你还能分配128mb的内存,你可以告诉GC不要回收,然后一段时间后,即使我们在这个预算中分配内存,也不会发生GC。它甚至可以防止在内存分配不足时阻塞完全垃圾收集:

代码执行完毕,最后一次调用a:

您可以恢复GC行为。

此外,我们还可以指定GC在运行时的模式,以最大限度地提高性能:

此外,我们甚至可以直接在堆内存中执行代码,创建一个JIT。NET中,直接从内存中创建一个可执行区,然后在其中插入一段代码来添加两个32位整数:

除此之外,C#还有无数底层的编写方法与操作系统交互,甚至使用C#的编译器解除与自身标准库的链接,直接从0开始构建基本类型,然后通过NativeAOT编译出完全无GC、可以在裸机硬件上执行引导系统的EFI固件也是没有问题的。

此外,还有ILGPU,它允许您直接在GPU和嵌入式设备上运行C#代码。我可以直接操作I2C,PWM,GPIO等等,就不多举例了。

而C#已经进入了roadmap的后续更新:允许引用字段的声明,增加类型表示定长内存,允许传递数组时消除数组分配,允许栈上任何对象的分配等等。,所有这些都在改善这些基础性能设施。

那个 这是我认为C#和Java最大的区别。

在C#中,当你不 t需要这些东西,它们好像从来不存在,允许动态类型,不断吸收各种功能特性,各种语法糖加持。简单性和灵活性。;甚至不会失去Python,所以你可以愉快而简单地编写各种代码。一旦你需要,你就可以拥有从上到下几乎完全的控制能力,而这些能力会让你在必要的时候不用思考各种奇怪的变通方法,直接把机器榨干,达到C和C的性能,甚至因为运行时PGO而超过C和C的性能。

c语言可以泛型编程吗?如何实现?

泛型编程是一种非常常见的编程方法。主要目的是实现静态绑定,使函数可以接受不同类型的参数,并在编译时确定正确的类型。

许多语言都支持泛型编程。例如,在C中,可以使用函数模板和类模板来实现泛型编程。在单一继承的语言中,如Java、Objective-C或C#,也可以使用similar和NSObject类型进行编程。在具有类型推理功能的编程语言(如Swift)中,可以直接使用泛型编程。

但C语言是高级语言编程的基础语言,如何用C语言实现泛型编程确实是个问题。首先,C语言不支持函数重载和模板类型,实现起来真的很难。

0x01通用指针简介(void *)

Void *是C语言中的一种类型。众所周知,在大多数编程语言中,void类型表示所谓的空类型,比如一个函数返回一个空类型void,这是非常常见的用法。

注意:void的返回值并不意味着没有返回值,而是意味着返回一个空类型,这也是为什么你仍然可以在这些函数中使用return语句的原因。只有某些语言中的构造函数和析构函数没有返回值。在这些函数中,不允许使用return语句。它们是显著不同的。Objective-C是一种独特的语言,它的类初始化方法是一种普通的方法,返回值是instancetype(当前类的指针类型)。

Void *可能有点不为人知。void *可以表示C语言中任何类型的指针。毕竟对于一个内存单元的地址来说,它存储的所谓数据类型,只是一次取的字节数。只是不同,这些存储单元的地址本身并没有不同。下面会更好的体现这句话的意思。

void *的大小永远是一个字,就像普通的指针一样。具体大小因机器字长而异,例如32位机器为4字节,64位机器为8字节。

我还没有 t在16位8086机上验证了指针的大小,因为8086的地址是20位。有兴趣的可以回去试试。

个人认为指针大小还是16位,因为20位是物理地址,物理地址是从段地址和偏移地址计算出来的。汇编后,C语言中的指针可能只是变成了相对于段地址的偏移地址。毕竟对于8086来说,数据永远在DS段,而代码永远在CS段。(斜体表示未经核实的陈述)

在C语言中,其他常用类型的指针可以自动转换为void * type,而void * type只能强制转换为其他常用类型的指针,否则会出现警告或错误。

关于所谓的void *指向数组有一个特别大的坑,这里直接用代码解释。

void Swap(void *array,int x,int y,int mallocsize) {

void *temp malloc(mallocsize)

memcpy(temp,数组mallocsize*x,mallocsize)

memcpy(数组mallocsize*x,数组mallocsize*y,mallocsize)

memcpy(数组mallocsize*y,temp,mallocsize)

免费(临时)

}

这是一个经典的交换函数,借助临时变量temp,不过这个函数是通用的,memcpy的用法后面会介绍。需要注意的是,如果array指向一个数组,你可以 t直接用amparray[x]或者array x来获取指向x元素的地址,因为void *类型的默认指针偏移量是1,和char *一样,对于大多数类型都会造成错误。因此,在使用泛型类型时,我们必须知道它的原始长度。我们需要一个名为mallocsize的int类型参数来告诉我们这个值,并在计算指针偏移量时乘以它。这相当于C编程中的模板类型定义或者Java中的泛型参数。

同时要注意void *类型的指针,它可以 不要在任何时候被取消参考(或者老师过去叫什么 "获取内容 "在课堂上?),原因很明显:void类型的变量是不合法的。所以如果如果要解引用,必须先将其转换成普通类型的指针。当在数组中使用时,还应该注意解引用操作符优先于加法操作符,所以应该加上括号,如下所示:

int a *(数组mallocsize * x)

这段代码完美体现了C语言编程的丑陋。

0x02 sizeof运算符简介

Sizeof运算符相信学过C语言的朋友都很熟悉,但知道sizeof是运算符的人不多,返回的类型是size_t类型。sizeof运算符返回类型占用的空间量。这里唯一的要点是,如果你找到一个指针类型或数组名的大小(事实上,数组名是一个指针常量),返回的结果总是一个单词(见上文)。求一个结构类型的sizeof不是简单的结构中各种类型的sizeof之和,而是涉及到内存对齐的问题。我不 这里不想介绍了。详情请访问:如何理解struct的内存对齐?-智虎。

0x03 memcpy功能简介

Memcpy是一个经常和void *一起使用的函数,它的函数原型是:

void * memcpy(void *,const void *,size_t)

头文件属于string.h,可以看到,这个函数本身是以void * type作为参数和返回值的,其实很好理解。这是一个赋值和复制记忆的过程。将第二个参数指向的内存复制到第一个参数,复制的字节数由第三个参数指定。当然,第三个参数通常是通过sizeof运算符获得的,所以我赢了 这里就不举例了。我还没有 我没有研究过返回值,也没有研究过。;我没用过。如果有知道的朋友可以评论一下。

用0x04 C语言实现泛型编程

话虽如此,我们还没有 t还没有提到泛型编程。但是,如上所述,一般的思路是使用void * type作为泛型指针,然后使用类似于mallocsize的参数来指定占用的内存大小。占用的内存大小是通过sizeof运算符获得的。如果需要赋值,可以使用memcpy函数来完成。下面是一个直接的例子,这是一个通用的快速排序来说明这些问题:

#ifndef Compare_h

#定义比较_h

#包含ltstdio.hgt

#包含JCB.h

int IsGreater(void *x,void *y)

int IsGreaterOrEqual(void *x,void *y)

int IsSmaller(void *x,void *y)int IsSmallerOrEqual(void *x,void *y)

#endif /* Compare_h */

//

// Compare.c

//作业调度程序

//

//鲁创作于2017/11/16。

//版权?2017鲁饶威。保留所有权利。

//

#包含比较. h

int IsGreater(void *x,void *y) {

return *(int *)x gt *(int *)y

}

int IsGreaterOrEqual(void *x,void *y) {

return *(int *)x gt *(int *)y

}

int IsSmaller(void *x,void *y) {

return *(int *)x lt *(int *)y

}

int IsSmallerOrEqual(void *x,void *y) {

return *(int *)x lt *(int *)y

}

//

// QuickSort.h

//作业调度程序

//

//鲁创作于2017/11/16。

//版权?2017鲁饶威。保留所有权利。

//

#ifndef快速排序_h

#定义快速排序_h

#包含ltstdio.hgt

#包含ltstdlib.hgt

#包含ltstring.hgt

#包含比较. h

void QuickSort(void *array,int left,int right,int mallocsize)

#endif /* QuickSort_h */

//

// QuickSort.c

//作业调度程序

//

//鲁创作于2017/11/16。

//版权?2017鲁饶威。保留所有权利。

//

#包含快速排序. h

void Swap(void *array,int x,int y,intmallocsize) {

void *temp malloc(mallocsize)

memcpy(temp,数组mallocsize*x,mallocsize)

memcpy(数组mallocsize*x,数组mallocsize*y,mallocsize)

memcpy(数组mallocsize*y,temp,mallocsize)

免费(临时)

}

int QuickSortSelectCenter(int l,int r) {

返回(左侧)/2

}

int quick sort partition(void * array,int l,int r,int mallocsize) {

int left l

int右r

void *temp malloc(mallocsize)

memcpy(temp,数组mallocsize*right,mallocsize)

while(左左右){

while(IsSmallerOrEqual(array mallocsize * left,temp) ampamp left lt right) {

左边的

}

if(左lt右){

memcpy(数组mallocsize*right,数组mallocsize*left,mallocsize)

正确

}

while(IsGreaterOrEqual(array mallocsize * right,temp) ampamp left lt right) {

正确

}

if(左lt右){

memcpy(数组mallocsize*left,数组mallocsize*right,mallocsize)

左边的

}

}

memcpy(数组mallocsize*left,temp,mallocsize)

向左返回

}

void QuickSort(void *array,int left,int right,int mallocsize) {

if (leftgtright) {

返回

}

中间快速kSortSelectCenter(左,右)

交换(数组、中心、右侧、mallocsize)

center QuickSortPartition(数组,左,右,mallocsize)

快速排序(数组,左,中心-1,mallocsize)

快速排序(数组,中心1,右侧,mallocsize)

}

这里有个悬念,明明可以直接比较,何必用很多函数来完成,也就是关于Compare.h使用的问题,答案会在下面揭晓。

0x05泛型的协议问题

刚才那个问题涉及到一个通用的协议问题,我借用了Objective-C的一个概念在这里详细阐述。就像那个问题一样,既然我的快速排序是泛型的,那我怎么保证传入的实际泛型参数一定是可比较的呢?比如很明显int,float,double是可以比较的,我们也理解char使用ASCII编码方案的比较,字符串类型甚至可以比较。但是如何比较其他语言中的对象呢?这是一个问题。在C中,我们可以重载运算符,所以我们仍然可以使用比较运算符,并使用运算符来重载函数。但是Java和Objective-C呢?而如果传入的泛型参数没有实现对应的运算符重载函数呢?这时,有必要引入一个协议的概念。简单地说,如果一个类型想要成为一个排序泛型函数的泛型参数,你必须实现一个可比较的协议。这个协议在Swift语言中叫做Comparable,这样在编译的时候,编译器就会知道这个泛型参数是可以比较的,从而完成我们的操作,否则就会出错。这是泛型中的协议问题。

0x06摘要

C语言的泛型编程以void *为泛型类型,本质上是一个泛型指针。

C语言中的泛型编程需要知道泛型类型变量的内存大小,这可以通过sizeof获得并传递给泛型函数。

在C语言的泛型编程中,要注意数组的偏移量。void *的默认偏移量是1,对于大多数类型来说是错误的,需要自己编程转换。

Memcpy函数用于在C语言的泛型编程中复制和赋值泛型变量。

在C语言的泛型编程中,我们也需要注意协议问题,但是在C中,我们只能自己编写函数来定义,可以使用其他语言现成的接口或协议。

类型 代码 指针 内存

版权声明:本文内容由互联网用户自发贡献,本站不承担相关法律责任.如有侵权/违法内容,本站将立刻删除。