大端和小端

大端和小端

2013-11-15 13:07 by soar., 446 阅读, 1 评论, 收藏编辑

  在计算机中是以字节为单位,每个地址对应一个字节,一个字节8bit。在C中,除了8bit的char以外,还有16bit的short,32位的int,64位long,当然具体要由编译器决定,可以通过sizeof来获取不同类型在内存中占用的字节数。在计算机系统中,当物理单位的长度大于1个字节时,就要区分字节顺序。常见的字节顺序有两种:Big Endian(High-byte first)和Litter Endian(Low-byte first),当然还有其他字节顺序,但不常见,例如Middle Endian。

一、最高有效位、最低有效位

  要理解Big Endian和Little Endian,首先要搞清楚MSB和LSB。

  1、MSB(Most Significant Bit)最高有效位

    在一个n位二进制数字中n-1位,也就是最左边的位。

  2、LSB(Least Significant Bit)最低有效位

    指最右边的位。

  例如:一个int类型的整型123456789

    二进制表达方式:0000 0111 0101 1011 1100 1101 0001 0101(从右向左,每4bit对齐,最左边(高位)不够用0补齐)

    十六进制表达方式:0 7 5 B C D 1 5

    按照上述关于MSB和LSB的意思,在二进制表达方式中,bit从0开始,从右向左,bit0为最低有效位,而bit23为最高有效位。而我们一般称左边的0x07为高位字节,0x15为低位字节。

    再通俗一点解释就是:8421码的,8这端为高位,1这端为低位,相应的字节则分别称为高位字节和低位字节。

二、内存地址

  在内存中,多字节对象都是被存储为连续的字节序列。例如在C语言中,一个类型为int的变量n,如果其存储的首个字节的地址为0x1000,那么剩余3个字节的地址将存储在0x1001~0x1003。总之,不管具体字节顺序是以什么方式排列,内存地址的分配一般是从小到大的增长。我们常把0x1000称为低地址端,把0x1003称为高地址端。

三、大端和小端

  搞清楚MSB、LSB、高位字节、低位字节、内存地址之后,再理解大端和小端,就相当容易了,先看看概念:

    小端Little Endian:低字节存放在低地址,低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

    大端Big Endian:高字节存放在低地址,即高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

  以二节中的例子int类型整数123456789为例:

    小端在内存中排列:0x15 0xCD 0x5B 0x07 (低位在前)

    大端在内存中排列:0x07 0x5B 0xCD 0x15 (高位在前)

  从例子中可以看出小端比较符合人的思维,而大端则看上去非常直观。

  注:

    1、例子中是假设编译器支持int为32位的前提下,如果是16位,那大端的排列则为:0xCD 0x15 0x07 0x5B。

    2、大小端一般是由CPU架构决定,常见的Intel、AMD的CPU使用的是小端字节序,而PowerPC使用的是大端字节序,有些ARM处理器还可以选择用大端还是小端模式,具体请自行查阅。

    3、c#中,字节序跟编译平台所在的CPU相关,例如在Intel x86 CPU架构的windows平台中,c#采用的小端序。而Java由于其JVM屏蔽了不同CPU架构导致的字节序差异,所以默认采用大端字节序。所以,大小端模式是由CPU决定,而编译器又可能会改变这种模式。

字节序 内存地址 int(16bit) int(32bit) 特点
小端 0x1001,0x1002,0x1003,0x1004 0x15 0xCD 0x5B 0x07 0x15 0xCD 0x5B 0x07 低地址端存储低位字节,低位在前
大端 0x1001,0x1002,0x1003,0x1004 0xCD 0x15 0x07 0x5B 0x07 0x5B 0xCD 0x15 低地址端存储高位字节,高位在前

四、网络字节序和主机字节序

  网络字节序(Network Order):TCP/IP各层协议将字节序定义为Big Endian,因此TCP/IP协议中使用的字节序通常称之为网络字节序。

  主机字节序(Host Order):整数在内存中保存的顺序,它遵循Little Endian规则(不一定,要看主机的CPU架构)。所以当两台主机之间要通过TCP/IP协议进行通信的时候就需要调用相应的函数进行主机序列(Little Endian)和网络序(Big Endian)的转换。

  如果是做跨平台开发时,双方需要协商好字节序,然后根据程序运行的环境,确定是否需要字节序转换。

例如约定的通讯字节序位Big Endian,默认的windows采用的Little Endian,那收到数据后就需要做转换操作。

五、C#位操作符

  这里简单记录一下C#的位操作符,方便以后自己查阅,也方便理解后面的讲解。

  1、按位与&

    1&0为0;0&0为0;1&1为1。

  2、按位或|

    1|0为1;0|0为0;1|1为1。

  3、按位取反~

    ~1为0;~0为1。

  4、按位异或^

    1^1为0;0^0为0;1^0为1。相等得0,相异等1。

  5、左移<<

    位左移运算,将整个数向左移若干位,左移后空出的部分用0补齐。

  6、右移>>

    位右移运算,将整个数向右移若干位,右移后空出的部分用0补齐。

六、C#中关于大端和小端的转换
  1、重复轮子

using System;

namespace Framework.NetPackage.Common
{
    /// <summary>
    /// 字节序转换辅助类
    /// </summary>
    public static class Endian
    {
        public static short SwapInt16(this short n)
        {
            return (short)(((n & 0xff) << 8) | ((n >> 8) & 0xff));
        }

        public static ushort SwapUInt16(this ushort n)
        {
            return (ushort)(((n & 0xff) << 8) | ((n >> 8) & 0xff));
        }

        public static int SwapInt32(this int n)
        {
            return (int)(((SwapInt16((short)n) & 0xffff) << 0x10) |
                          (SwapInt16((short)(n >> 0x10)) & 0xffff));
        }

        public static uint SwapUInt32(this uint n)
        {
            return (uint)(((SwapUInt16((ushort)n) & 0xffff) << 0x10) |
                           (SwapUInt16((ushort)(n >> 0x10)) & 0xffff));
        }

        public static long SwapInt64(this long n)
        {
            return (long)(((SwapInt32((int)n) & 0xffffffffL) << 0x20) |
                           (SwapInt32((int)(n >> 0x20)) & 0xffffffffL));
        }

        public static ulong SwapUInt64(this ulong n)
        {
            return (ulong)(((SwapUInt32((uint)n) & 0xffffffffL) << 0x20) |
                            (SwapUInt32((uint)(n >> 0x20)) & 0xffffffffL));
        }
    }
}

  2、BCL库支持的函数

    System.Net.IPAddress.HostToNetworkOrder、System.Net.IPAddress.NetworkToHostOrder,这两个函数的内部实现和上面重复轮子原理一模一样。

七、关于负数

  在计算机中,负数以其绝对值的补码形式表示,不明白可以查阅九中贴出的相关资源。关于负数的字节序跟一般整数的字节序处理没有任何区别。

八、关于汉字编码以及与字节序的关系

  1、对于gb2312、gbk、gb18030、big5,其编码某个汉字产生的字节顺序,由其编码方案本身决定,不受CPU字节序的影响。其实这几种编码的字节序和大端模式的顺序是一致的。

  在使用GB2312的程序通常采用EUC储存方法,以便兼容于ASCII。浏览器编码表上的“GB2312”,通常都是指“EUC-CN”表示法。
每个汉字及符号以两个字节来表示。第一个字节称为“高位字节”,第二个字节称为“低位字节”。
  “高位字节”使用了0xA1-0xF7(把01-87区的区号加上0xA0),“低位字节”使用了0xA1-0xFE(把01-94加上0xA0)。

  由于一级汉字从16区起始,汉字区的“高位字节”的范围是0xB0-0xF7,“低位字节”的范围是0xA1-0xFE,占用的码位是72*94=6768。其中有5个空位是D7FA-D7FE。
  例如“啊”字在大多数程序中,会以两个字节,0xB0(第一个字节)0xA1(第二个字节)储存。(与区位码对比:0xB0=0xA0+16,0xA1=0xA0+1)。

  2、UTF-8

      UTF-8和gb系列编码一样,其编码某个汉字产生的字节顺序,由其编码方案决定,不受CPU字节序的影响。无论一个汉字有多少个字节,它的字节序与编码顺序保持一致。

例如汉字”严”利用utf8编码过程:

1、已知“严”的unicode编码是4E25(100111000100101),根据utf8规则可以得知其utf8编码需要三个字节。

  即格式是“1110xxxx 10xxxxxx 10xxxxxx”

  第一个字节前三位表示了字符“严”被编码成utf8后的编码长度,有多长,则从左开始填多少个1,如果只有1个字节,则第一个位为0。

  对于编码后大于1个字节的符号,第一个字节的第四位为0,其他字节前两位均要求为10。

2、从”严“的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了“严”的utf8编码为“11100100 10111000 10100101”,转换成十六进制就是E4B8A5。

编码示例过程参考的原文:http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html

从上述过程可以看到,utf8的字节序已经由其编码方案决定,不受CPU字节序影响。

  3、Unicode

    Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。所以他没有要求如何存储编码后的字节,也就受CPU字节序的影响。

    Unicode的具体实现包括UTF-16、UTF-32(当然也包括UTF-8,但由于其编码方式和编码后的字节序与其他Unicode编码实现有较大区别,所以单独拿出来讲解的)。

  4、总结

    1、网络通讯

      在实际的网络通讯中,网络协议例如TCP是规定网络字节序(Network Order)是大端。而针对汉字具体使用什么编码,通讯双方要么提前约定好,要么就需要在数据包中标识好汉字具体使用的编码。

      如果在网络通讯中,涉及例如UTF16这样区分大小端的编码,除非按网络协议要求采用大端模式是,否则也要事先约定好,或者在数据包中标识好使用的字节序模式。

    2、文件

      文件的也会存储汉字,当然也要进行编码。如果采用UTF-16这样的有字节序模式区分的编码,编码规则要求可以在文件头部的BOM(Byte Order Mark)来标记。如果没有标记,除非事先知道字节序的模式,否则只能大小端都试一遍。

  Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。BOM是一个有点小聪明的想法:

  在Unicode编码中有一个叫做”ZERO WIDTH NO-BREAK SPACE”的字符,它的编码是FEFF。而FEFF在Unicode中是不存在的字符,所以不应该出现在实际传输中。UCS(Unicode的学名)规范建议我们在传输字节流前,先传输字符“ZERO WIDTH NO-BREAK SPACE”。

  这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。因此字符“ZERO WIDTH NO-BREAK SPACE”又被称作BOM。

  UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符“ZERO WIDTH NO-BREAK SPACE”的UTF-8编码是EF BB BF。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。

九、参考资源

  http://baike.baidu.com/view/1922338.htm

  http://www.cppblog.com/izualzhy/archive/2011/10/20/158784.html

  http://www.cnblogs.com/junsky/archive/2009/08/06/1540727.html(负数的二进制表示方法)

  http://www.cnblogs.com/augellis/archive/2009/09/29/1576501.html (sizeof)

  http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html(汉字编码)

  http://djt.qq.com/article/view/658?ADTAG=email.InnerAD.weekly.20130902(汉字编码)

 
 
分类: 网络编程

为了开始对实现既有接口的了解,我们就看一下IEnumerable和IEnumerator的作用,想一下,C#支持关键字foreach,允许我们遍历任何数组类型的内容:

//遍历数组的项
int[] myArray={10,20,30}
foreach(int i in myArray)
{......}

虽然看上去只有数组类型才能使用这个结构,其实任何支持GetEnumerator()方法的类型都可以通过foreach结构进行运算,举例说明,我们新建一个项目。

首先,我们创建一个类

复制代码
 public class Car
    {
        public int Speed{get;set;}
        public string PetName { get; set; }

        public Car(int s, string p)
        {
            this.Speed = s;
            this.petName = p;
        }
    }
复制代码

接下来我们再建一个类,用来在System.Array中保存一组Car类型

复制代码
public class Garage
    {
        private Car[] carArray = new Car[4];
        public Garage()
        {
            carArray[0] = new Car(30,"Rusty");
            carArray[0] = new Car(34, "Tom");
            carArray[0] = new Car(45, "KIMM");
            carArray[0] = new Car(50, "fRED");
        }
    }
复制代码

理想情况下,使用C#foreach结构迭代Garage对象中的每个子项比较方便。

复制代码
 static void Main(string[] args)
        {
            Garage carLot = new Garage();
            foreach (Car c in carLot)
            { 
                
            }
        }
复制代码

但是编译器会通知我们Garage类没有实现名为GetEnumerator()的公共定义。对象支持这种行为说明他们必须能够向调用方提供自己包含的子项。

//这个接口告知调用方对象的子项可以枚举
public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

可以看到,GetEnumerator()方法返回对另一个接口IEnumerator的的引用,这个接口提供了基础设施,调用方可以用来移动IEnumerable兼容容器包含的内部对象。

复制代码
 //这个接口允许调用方获取容器的一个子项
    public interface IEnumeratoe
    {
        bool MoveNext();
        object Current { get; }
        void Reset();
    }
复制代码

如果想修改Garage类来支持这些接口,我们可以手工实现这些方法,但是这很麻烦。虽然自己开发GetEnumerator() MoveNext() Reset()也没问题,但是有一个更简单的办法。因为System.Array类型和许多其它的类型都已经实现的IEnumerable和IEnumerator接口,我们可以简单委托请求到System.Array,如下所示

复制代码
   public class Garage:IEnumerable
    {
        private Car[] carArray = new Car[4];
        public Garage()
        {
            carArray[0] = new Car(30,"Rusty");
            carArray[0] = new Car(34, "Tom");
            carArray[0] = new Car(45, "KIMM");
            carArray[0] = new Car(50, "fRED");
        }

        public IEnumerator GetEnumerator()
        {
            //system.Array已经实现了IEnumerator
            return carArray.GetEnumerator();
        }
    }
复制代码

修改后,我们就可以使用foreach来遍历了。除此之外,GetEnumerator()被定义为公开的,对象用户可以与IEnumerator类型交互。

//手动与IEnumerator协作
IEnumerator i=carLot.GetEnumerator();
i.MoveNext();
car myCar=(car)i.Cuurent;
 

用Visual Studio开发跨平台应用:微软与Xamarin深度合作

 

美国旧金山当地时间11月13日,微软宣布与Xamarin达成战略合作,帮助全球范围内的移动开发者更好地利用Visual Studio在各大主流移动平台开发本地应用。Xamarin是一家致力于利用统一通用代码,帮助开发者在iOS、 Android、Windows和Mac平台创建本地应用的开发架构公司。Xamarin现在成为全球唯一一个利用Visual Studio开发iOS、 Android、Windows跨平台本地应用的软件解决方案,成功地把世界上最大的开发平台和世界最成功的移动平台连接起来。

移动应用市场的激烈竞争使得开发者们面临巨大压力,他们必须为消费者和企业用户开发出高质量和出色用户体验的应用,才能在竞争中脱颖而出。一个小小的Bug或程序崩溃都有可能让他们被用户永久抛弃。iOS和Android平台上数以千计的不同款设备让开发者们陷入碎片化陷阱,在不同型号设备上的应用调试和修改耗费了开发者的大量精力和宝贵资金。持续不断的系统升级和部分设备的版本停滞加速了移动平台的碎片化,让开发者们倍感头疼。

双方的战略合作涉及三个领域:

整合Xamarin技术和微软开发工具及服务,Xamarin将成为Visual Studio 2013的发布合作伙伴,以后也将同步支持微软最新版本Visual Studio。Xamarin在这一天还同时宣布完全加入微软iOS和Android上的Portable Library跨平台项目,让开发者更容易地在不同设备之间共享代码。

最近发布的Xamarin University培训计划将向微软MSDN订阅用户免费提供。这一为期30天的培训课程将帮助开发者更好地开发高品质iOS和Android应用。这一价值1995美元的课程将于2014年1月开幕,为MSDN订阅者提供有限数量的免费培训机会。

MSDN的个人或团队订阅者将拥有各自的价格及审核选项。

此次战略合作可以让开发者能够利用C#、Visual Studio 2013和Windows Azure灵活快速地进行跨平台应用开发。利用标准化的Xamarin和C#移动应用开发程序,75%的本地开发代码可以实现跨设备共享。Xamarin支持iOS和Android平台API的100%共享——所有使用Objective-C 或Java能做到事情,现在用C#和Xamarin也能做到。

Xamarin目前在全球70个国家累积了超过44万名开发者,超过2万个付费账户及120个咨询合作伙伴。

微软与Xamarin将在美国20个城市举行路演活动,向美国各地的开发者展示如何运用双方的合作成果进行跨平台应用开发。

10月底微软宣布与跨平台架构公司Corona Labs达成了合作,Corona将携30万名开发者在2014年初支持Windows移动平台。这是半个月时间内,微软与著名跨平台架构公司达成的第二项合作计划,对广大移动平台开发者来说是一项值得关注的福利,只要对代码进行小幅修改,他们的应用就能够支持所有主流移动平台,帮助他们取得更大的成功。

不过Xamarin与微软的合作是一次更加紧密的战略对接,是双方优势开发工具的强强联合,具有更加深远的战略意义,与Corona支持Windows移动平台大不相同。

这两项跨平台开发战略合作将进一步加快Windows移动生态系统的繁荣发展,广大Windows移动平台用户可以期待更多精品应用和游戏的到来。

 
分类: C#

【perl】 读代码笔记

1.use strict 

  当你的程序有一定的行数时,尤其是在一页放不下时,或者是你找不到发生错误的原因时,在你的脚本的开头 '#!/usr/local/bin/perl' 后面加上这句就行,use strict。

  他帮你寻找因为错误拼写造成的错误。比如错误使用了'$date' 变量,但实际上你在程序中已声明的是 '$date' 变量,这个错误就很难发现。同样,use strict 迫使你把变量的范围缩到最小,使你不必担心同名变量在程序的其它部份发生不良作用。尽管这是 my 的功能,但是如果你使用 use strict 的话,它会强迫你用 my 声明变量,来达到上述目的。

  常见的错误信息一般如下:Global symbol "$xxx" requires explicit package name at ./tst line 5.这就是需要我们马上去解决的错误。如果出现类似于 'Server Error' 之类的错误,请检查一下你的 web 服务器上的 error logs, 或用 CGI::Carp包。 

2.shift

  类别中建立新物件的new()方法:
  sub new{
    my $class=shift;
    my $self={};
    bless $self;
  }
  shift 的意思就是把整个 array 的第一个 value 取出,并将 array 长度减一(有点像 pop out),也就是说 sub new 是传一串值进入,然后 my $class 会被 assign 最前面的那个传入值.
 sub log10 {
    my $n = shift;
    return log($n)/log(10);
  }

3.open
  在Perl中可以用open或者sysopen函数来打开文件进行操作,这两个函数都需要通过一个文件句柄(即文件指针)来对文件进行读写定位等操作。

  1:读:open(文件句柄,"<文件名")/open(文件句柄,"文件名"),前提文件必须已经存在,否则会返回0,出错信息在$!中。
  2:写:open(文件句柄,">文件名"),文件如果不存在,那么创建之,如果存在,内容被清空,长度截为0,$!中有出错信息。
  3:追加:open(文件句柄,">>文件名"),基本同写,但有一点,文件中的内容不会被清空,新的内容会追加到原文后面。
  4:读写:open(文件句柄,"+<文件名"),通过“+<”模式,你可以既可以读文件,又可以写文件。你可以通过tell() 函数在文件内部移动,通过seek()函数进行定位。如果文件不存在,就会被创建。如果文件已经存在,原来的数据不会被清除。
    open(文件句柄,"+>文件名"),通过“+》”模式,可以同时读写文件,但与上面不同的是,它是破怀性写,会清除原来的内容。
复制代码
  my $conf = "D:/test.txt";
  if(-e $conf){  
    open(FILE, "<$conf") or die("No such file"); 
      while($LINE = <FILE>){  
        print $LINE;
        push(@del,$LINE);
      }
    close(FILE);
  }
复制代码

4.Dumper

  Data::Dumper模块主要用途是:给出一个或多个变量,包括引用,以PERL语法的方式返回这个变量的内容。
  例如这里有个很复杂的hash,数据结构很复杂,我想看看这个hash里面的内容。除了常见的方式(直接用print或者编历keys然后打印), 我们也可以使用Data::Daumper->Dump([\%hash])的形式。
  同时,模块中定义了很多的配置参数,让用户可以调整打印格式。
    $Data::Dumper::Indent    这个设置打印的缩进格式,可以设置成0,1,2和3。用户可以自己尝试下。
    $Data::Dumper::Terse    如果设置这个变量,则不打印变量的名字,只打印变量的内容。
    $Data::Dumper::Maxdepth    不超过这个变量的限制深度,才打印变量的内容。
  结合Data::Dumper和Storable存储和重新获取数据。你可以用U盘将数据拷走,再在其他服务器上展开。
复制代码
#!/usr/bin/perl -w
use Data::Dumper;
use Storable;
my $a = "good";
my @myarray = ("hello", "world", "123", 4.5);
my %myhash = ( "foo" => 35,
   "bar" => 12.4,
   "2.5"=> "hello",
   "wilma" => 1.72e30,
   "betty" => "bye/n");
print Dumper($a) ."
"x2;
print Dumper(@myarray) ."
"x2;
print Dumper(%myhash) ."
"x2;
print Dumper((%myhash, @myarray)) ."
"x2;
###use Storable
print "
method 1,use Storable retrieve data:
";
store %myhash,'./file.txt'; #保存数据
my $hashref=retrieve('./file.txt'); #重新获取数据
print Dumper(\%$hashref);
复制代码

 5.献给新手:关于学习Perl 

  在Web开发语言非常多。在PHP和JavaScript语言在网页开发上大行其道的同时,还有另一种形式开发Web,就是利用C/C++语言进行编写CGI脚本开发网页。 

  从项目应用来看,用Perl、C++等语言开发网页是比较费事的事情,编写自定义CGI脚本需要相当多的编程技巧。开发周期非常漫长,很难满足快速多变的用户需求,往往完成一个项目需要很长的周期,显然与当今的速度至上的时代有很大差距。 

  如果有读者是从事或者欲将从事CGI开发,建议还是一开始就使用C++或perl语言进行开发,虽然用C语言编写的CGI程序具有执行速度快、安全性高(因为C语言程序是编译执行且不可被修改)等特点,但是C语言缺乏强有力的字符串处理能力,这个会影响网页处理速度。在执行过程中,由于每一次对于动态内容的请求都需要启动一个新的CGI程序,因而会增加Web服务器的负担,所以CGI的一个很大缺陷是容易影响Web服务器的运行速度。 

  用此方式建立的网站,当多个用户去访问Web方式的时候,它的处理能力确实不尽人意。但是如果您是一位专门从事设备控制,操作配置开发的工程师,这个方式是最好的选择,因为该形式,在多数情况下只是几个管理员进行操作。不需要处理多用户并发操作。另一个特点就是不受客户端限制,只要有浏览器,就可以配置操作。


























 
 
 
标签: perl
原文地址:https://www.cnblogs.com/Leo_wl/p/3427363.html