在这个帖子的11楼:http://topic.csdn.net/u/20081223/18/9E5CEC7B-5025-460A-BA30-2BC85704BEB8.htmlunsigned说:所有指针都一律定义为Long,但是自己要记得,或者做个标记,需要用到的时候,再通过VarPtr等传入指针(实体一定不能被释放掉)。
eglic问:那对于结构里面,固定长度的字符串呢?这个没办法用指针了吧? 
unsigned说:使用byte array,对于unicode应该可以使用[string * N],当然也可以使用两倍的Byte Array或者Integer Array然后Copy进去,实际上对于结构最关键的就是结构对齐。在这篇文章的后部:http://www.vbgood.com/vb.good/article-do-view-articleid-2386.html
作者写道:有一点需要特别注意,在VB中指针的算法非常重要。此外,你必须计算元素的大小,因为VB不会帮你完成这项工作。你还必须处理缺乏无符号长整型数据类型的问题。下面的函数实现了无符号算法
Function UnsingedAdd (ByVal Start As Long, ByVal Incr As Long) As Long
    Const SignBit As Long = &H80000000
    UnsignedAdd=(Start Xor SignBit) + Incr Xor SignBit
End Function没看懂的是标记为红色的部分,貌似他们在说相同的东西:
(1)unsigned说:“对于结构最关键的就是结构对齐”。这是什么意思?
(2)第二篇文章说:“在VB中指针的算法非常重要”。这个指针的算法是什么?能举个例子说明么?一般什么时候用?
(3)第二篇文章说:“你必须计算元素的大小”。这又是什么意思
(4)能逐字逐句解释一下上面这个“无符号算法”的意思么?

解决方案 »

  1.   

    (1)VB 中变量的字节对齐大概就是‘右对齐’~~~~~~
    说笑的,“高字节对齐”,每个变量会占用4字节的空间,且是“字边界对齐”:
    Private Sub Command1_Click()
        Dim t As Boolean, strTest$, bB As Byte, iLong&, iT%, iKK&
        strTest = "123456"
        Debug.Print Hex$(VarPtr(t))
        Debug.Print Hex$(VarPtr(strTest)), Hex$(StrPtr(strTest))
        Debug.Print Hex$(VarPtr(bB))
        Debug.Print Hex$(VarPtr(iLong))
        Debug.Print Hex$(VarPtr(iT))
        Debug.Print Hex$(VarPtr(iKK))
        strTest = strTest & "ABCD"
        Debug.Print Hex$(VarPtr(strTest)), Hex$(StrPtr(strTest))
    End Sub
    输出:
    12F442      (t 的地址,分配12F440~12F443,使用12F442~12F443)
    12F43C   190484 (strTest 的地址,分配12F43C~12F43F,全用。字符串内容存放190484起的地方)
    12F43A      (bB 的地址,分配12F438~12F43B,使用12F43A)
    12F434      (iLong 的地址,分配12F434~12F437,全使用)
    12F432      (iT 的地址,分配12F430~12F434,使用12F432~12F434)
    12F42C      (iKK 的地址,分配12F42C~12F42F,全使用)
    12F43C   1900FC (strTest实际内容的地址发生了变化,可见变长字符串运算是要重新分配内存的)
    当然数组元素的存放应该是连续的,只是首址按双字对齐。(2)VB中本身不支持“指针变量”,因此相应的运算就要你自己处理。你要清楚你所面对的数据类型占用的空间大小。
    比如 C/C++ 中,指针p++:如果p所指类型是 int、long、flat及各种类型的指针时,它的地址会移动四字节;而如果它所指类型是char,则只会移动一字节。(3)在VB中,用 Type 定义数据类型后,用它声明的变量,你就得仔细分析一下它每个变量会占用多少空间了。并且有时候跟你所想的完全不一样!而在 C/C++ 中,你几乎用不着操心这个问题。
      

  2.   

    (1)unsigned说:“对于结构最关键的就是结构对齐”。这是什么意思?  VB:
       type t1
         b as byte
       end type   len(t1) = 1 VC:
       struct t1{
         char a;
       };   sizeof(t1) == 4
      明白了没?结构对齐就是这个意思。因为VC会把结构按 4 或 8 字节对齐 VB不会,如果你不注意这个问题,把VB的结构直接传递到API中会导致出错。
        (2)第二篇文章说:“在VB中指针的算法非常重要”。这个指针的算法是什么?能举个例子说明么?一般什么时候用?  在VC中,你取得一个数组的某一元素的指针, 
      如 int a[888];
         int *p = & a[0]; 你可以通过用 p+5 得到 a[5] 的地址,  但是在VB中,这样做是达不到目的的。 在VB中想通过一个数组元素的地址算出另一元素的地址就是“这个指针的算法”,初学者不建议玩这个。 
      

  3.   

    (1)如果你熟悉C语言的位段,这个就好理解了。为了处理方便,适应32位计算机的特点,结构体的成员是按照32位对齐的。比如: 
    Private Type Strc 
        a as Byte 
        b as Integer 
        c as Long 
        d as Byte 
    End Type 
    按理说,访问成员d,从结构首地址算起,应该偏移7个字节。然而这是错的!!
    因为VB会按32位对齐分配结构,a--1Byte,b--2Bytes,还差1Byte,留空(当然不能把c截断啦),所以c安排到了首地址开始偏移32位的位置。后面依此类推。这样,访问成员d,需要偏移8字节。
    还有,涉及到结构中包含数组时,也要遵循这个原则。(2)VB没有C语言的指针类型,从而带来的问题是,在VB中,对你声明的长整形指针变量自加自减只是机械地加了“1”而已,并没有像C编译器那样,会根据指针指向的数据类型自动地加减这个数据类型的字节数。
    比如:
    int a[2] = {99, 88};
    int *p = a;
    p++;
    a的每个元素是两个字节,p自加后,指向a[1]。因此,如果是在VB里,想指向a(1),需要这样:
    Dim a(1) As Integer
    a(0) = 99
    a(1) = 88
    Dim p As Long
    p = VarPtr(a(0))
    p = p + 2
    其实就是问题(3)里的“你必须计算元素的大小”。(3)参照(2)(4)因为VB把Long型全部看作带符号的,因此范围是[-80000000, 7FFFFFFF]。
    整个函数的意思就是,把Start看作无符号数进行自加自减操作(注意Incr可正可负),返回值也看作无符号数(虽然用Debug等输出时带符号,就是说有时会显示为负数,但不影响应用)。
      

  4.   

    1.相关结构对齐,可以通过计算机组成原理等相关内存访问及内存访问优化等章节,或者一些针对性讲述内存管理及优化的书籍或文章,或者比如《代码优化:有效使用内存》这本书也有比较详细的介绍。主要是主流编译器为了内存访问的优化,会按机器字长进行内存对齐,从而导致某些结构例如:
    typedef struct _tagMyStruct{
      unsigned char x;
      unsigned char y;
      int z;
    }MyStruct;
    看上去只有 sizeof(unsigned char) +  sizeof(unsigned char) + sizeof(int) = 1 + 1 + 4,但是在Win32平台下,在没有强制做存储压缩的情况下,编译器却实际上让它占用了8字节的空间。x和y各占一个字节,所以x和y被分到同一个机器字长的空间当中,由于z是int,刚好是一个机器字长,为了访问z的时候能够一次性读入cache,而不是先从第一个机器字长的空间当中读取半个z,然后再读取第二个机器字长的空间才读取到后半个z,编译器就把z直接放到了下一个机器字长的空间当中,从而就导致了y和z中间隐藏了2个字节的空间。在VB当中,由是以压缩存储的,也就是说
    type MyStruct
      x as byte
      y as byte
      z as long
    End Type
    这样一个结构,在VB当中就是6个字节,从而如果你在调用一个API的时候,刚好碰到上面的C结构,那么你就很有可能会把一个在C当中占了8字节的结构定义成了只有6个字节,如此你在调用API的时候,就会导致内存访问溢出,后果是非常严重。正确地做法应该是:
    1).在定义结构的时候注意字节对齐,尽可能对定义的结构当中的“空洞”进行填充,例如定义成:
    typedef struct _tagMyStruct{
      unsigned char x;
      unsigned char y;
      short reserved;
      int z;
    }MyStruct;
    养成良好的习惯;
    2).在与第三方进行接口对接的时候,很难加以强制别人进行遵守,所以应该在使用时尽量分析清楚,或者了解清楚,从而定义出一个完全匹配的结构
    type MyStruct
      x as byte
      y as byte
      reserved as integer
      z as long
    End Type2.没有什么“指针算法”这一说法,如果没有理解错,原意应该是对指针偏移进行正确计算。指针只是一个形象的说法,实际在内存当中就是一个地址,对于VB语言本身地址操作是比较弱的,但是可以借助于API,以及VarPtr,StrPtr,以及address of等等操作符做到。在如C/C++等当中,当定义好一个结构指针类型之后,进行指移偏移运算的时候,会以结构大小进行偏移移位:
    typedef struct _tagMyStruct{
      unsigned char x;
      unsigned char y;
      short reserved;
      int z;
    }MyStruct,*LPMyStruct;LPMyStruct P;
    P = 0;
    P ++;//这里是P = (LPMyStruct)((unsigned long)P + sizeof(MyStruct)),而不是P = (LPMyStruct)((unsigned long)P + 1);而在VB当中则完全需要自己进行计算。个人理解这里所指的“指针算法”应该是一个相对性的计算方式。3.参照14.最高位为符号位,当通过xor把符合位去掉时就可以把一个无符号大数缩小,然后就可以进行运算,最后再把符合位替换回去,就相当于,先减2147483648,然后进行计算,最的再加回去。这里需要满足的是两个条件:
    1).结果不能溢出;
    2).start为大于2147483648的一个无符号数
    3).Incr是一个较小的正数
      

  5.   

    vb 和其他几乎所有高级语言一样都会对结构自动补齐
    对于地址对齐的问题,这根本就不用考虑了>在VB中指针的算法非常重要。此外,你必须计算元素的大小
    这个说法好像不成立,要么就是我没理解他的意思那个小函数...
    把两个有符号的数格式为无符号的数然后相加,但返回的结果还是被格式化为long,以至于结果不正确
    不知道意义在哪...
      

  6.   


    VC: 
      struct t1{ 
        char a; 
      };   sizeof(t1) == 1
      

  7.   

    (4)的函数正好是用来对指针自加自减的。
    具体是这样的:
    UnsingedAdd = (Start Xor SignBit) + Incr Xor SignBit
    先反转Start的符号位(即最高位)(欺骗VB,省得溢出),然后和Incr相加,结果的符号位再反转。
    讨论(以VB的带符号的补码运算机制):
    A)若Start>0,Incr>0或Start<0,Incr<0
    反转后相加时一正一负,不会溢出
    B)若Start>0,Incr<0
    反转后相加时,只要Start绝对值大于Incr绝对值,就不会溢出。也正符合指针没有负地址的要求。
    这也正是这个函数的妙处。从而也说明了Incr可以是负数!
    C)或Start<0,Incr>0
    反转后相加时,同样只要Start绝对值大于Incr绝对值,就不会溢出。
    在实际应用中,Start<0的情况不会出现。
      

  8.   

    就是说Start一律视为无符号数,即使最高位是1,所以认为Start <0的情况不会出现。
      

  9.   

    to 楼上各位,受教了!发言太精彩,不加分感觉不足够表达谢意,所以待有分了再结帖,现在太穷的说。to soyokaze:"(4)的函数正好是用来对指针自加自减的。" 这句话解释得太到位了,怪不得作者把那个函数放在那个地方。谢谢。to unsigned and soyokaze: 两位前辈都提到:“结果的符号位再反转”“最后再把符合位替换回去”。我怎么没看出来有这一步呢?Function UnsingedAdd (ByVal Start As Long, ByVal Incr As Long) As Long
        Const SignBit As Long = &H80000000
        UnsignedAdd=(Start Xor SignBit) + Incr Xor SignBit
    End Function
    这个明明只异或了一次啊?
      

  10.   

    另外 to soyokaze: 你在3楼的发言,举的第一个例子,说VB在内存安排上也是要填空的,不知是不是笔误?和unsigned的说法貌似不一致。
      

  11.   

    TO CHEN8013: 你的代码我运行出来怎么是这样的结果啊(我是在VBA里运行的,VBA是VB6的子集)Public Sub test()
        Dim t As Boolean, strTest$, bB As Byte, iLong&, iT%, iKK&
        strTest = "123456"
        Debug.Print "Hex$(VarPtr(t)) : " & Hex$(VarPtr(t)) & " : boolean 2字节"
        Debug.Print "Hex$(VarPtr(strTest)) : " & Hex$(VarPtr(strTest)), "Hex$(StrPtr(strTest)) : " & Hex$(StrPtr(strTest)) & " : 10字节加字符串长度"
        Debug.Print "Hex$(VarPtr(bB)) : " & Hex$(VarPtr(bB)) & " : 1字节"
        Debug.Print "Hex$(VarPtr(iLong)) : " & Hex$(VarPtr(iLong)) & " : 4字节"
        Debug.Print "Hex$(VarPtr(iT)) : " & Hex$(VarPtr(iT)) & " : integer 2字节"
        Debug.Print "Hex$(VarPtr(iKK)) : " & Hex$(VarPtr(iKK)) & " : 4字节"
        strTest = strTest & "ABCD"
        Debug.Print "Hex$(VarPtr(strTest)) : " & Hex$(VarPtr(strTest)), "Hex$(StrPtr(strTest)) : " & Hex$(StrPtr(strTest)) & " : 10字节加字符串长度"
    End Sub输出:Hex$(VarPtr(t)) : 13F772 : boolean 2字节
    Hex$(VarPtr(strTest)) : 13F76C      Hex$(StrPtr(strTest)) : CBA0B24 : 10字节加字符串长度
    Hex$(VarPtr(bB)) : 13F76A : 1字节
    Hex$(VarPtr(iLong)) : 13F764 : 4字节
    Hex$(VarPtr(iT)) : 13F762 : integer 2字节
    Hex$(VarPtr(iKK)) : 13F75C : 4字节
    Hex$(VarPtr(strTest)) : 13F76C      Hex$(StrPtr(strTest)) : DD41924 : 10字节加字符串长度
      

  12.   

    x xor &H80000000
    xor的效果就是当相同时取反,如果x的符合位为1则取反,即被替换成0,就相当于减去了&H80000000,如果不为1,则保持原来的值不变.
      

  13.   


    那“最后再把符合位替换回去”,这个是如何做到的呢?只替换xor了一次,没看到替换回去啊?
      

  14.   

    to chen8013:你的代码我多加了些debug注释(因为我对数据类型的字节数不熟悉:),别的没动。13楼贴的有点乱,你看这个:Public Sub test()
        Dim t As Boolean, strTest$, bB As Byte, iLong&, iT%, iKK&
        strTest = "123456"
        Debug.Print "Hex$(VarPtr(t)) : " & Hex$(VarPtr(t)) & " : boolean 2字节"
        Debug.Print "Hex$(VarPtr(strTest)) : " & Hex$(VarPtr(strTest)), "Hex$(StrPtr(strTest)) : " & Hex$(StrPtr(strTest)) & " string: 10字节加字符串长度"
        Debug.Print "Hex$(VarPtr(bB)) : " & Hex$(VarPtr(bB)) & " byte: 1字节"
        Debug.Print "Hex$(VarPtr(iLong)) : " & Hex$(VarPtr(iLong)) & " long: 4字节"
        Debug.Print "Hex$(VarPtr(iT)) : " & Hex$(VarPtr(iT)) & " integer: 2字节"
        Debug.Print "Hex$(VarPtr(iKK)) : " & Hex$(VarPtr(iKK)) & " long: 4字节"
        strTest = strTest & "ABCD"
        Debug.Print "Hex$(VarPtr(strTest)) : " & Hex$(VarPtr(strTest)), "Hex$(StrPtr(strTest)) : " & Hex$(StrPtr(strTest)) & " : 10字节加字符串长度"
    End Sub输出:
    Hex$(VarPtr(t)) : 13F772 : boolean 2字节
    Hex$(VarPtr(strTest)) : 13F76C      Hex$(StrPtr(strTest)) : C8C1C24 string: 10字节加字符串长度
    Hex$(VarPtr(bB)) : 13F76A byte: 1字节
    Hex$(VarPtr(iLong)) : 13F764 long: 4字节
    Hex$(VarPtr(iT)) : 13F762 integer: 2字节
    Hex$(VarPtr(iKK)) : 13F75C long: 4字节
    Hex$(VarPtr(strTest)) : 13F76C      Hex$(StrPtr(strTest)) : E35A20C : 10字节加字符串长度
      

  15.   

    To: LZ(13F)
      我是在 VB IDE环境下运行输出的。
      输出的地址不同是正常现象,这肯定跟具体环境有关。
      只是你看一下:相邻变量的地址差值,在不同的环境下也是相同的,说明对变量分配的字节空间是确定的,且分配规则相同。
      红色的那个地址是跟系统资源有关系的,即使在同一台机器上,不同的时候运行,它也会有变化的。
    ============================================================================
                           12F442      (t 的地址,分配12F440~12F443,使用12F442~12F443) 
                           12F43C  190484 (strTest 的地址,分配12F43C~12F43F,全用。) 
                           12F43A      (bB 的地址,分配12F438~12F43B,使用12F43A) 
                           12F434      (iLong 的地址,分配12F434~12F437,全使用) 
                           12F432      (iT 的地址,分配12F430~12F434,使用12F432~12F434) 
                           12F42C      (iKK 的地址,分配12F42C~12F42F,全使用) 
                           12F43C  1900FC  (变长字符串运算是要重新分配内存的) 
    -----------------------------------------------------------------------------
    Hex$(VarPtr(t))       : 13F772 : boolean 2字节 
    Hex$(VarPtr(strTest)) : 13F76C   Hex$(StrPtr(strTest)) : CBA0B24 : 10字节加字符串长度 
    Hex$(VarPtr(bB))      : 13F76A : 1字节 
    Hex$(VarPtr(iLong))   : 13F764 : 4字节 
    Hex$(VarPtr(iT))      : 13F762 : integer 2字节 
    Hex$(VarPtr(iKK))     : 13F75C : 4字节 
    Hex$(VarPtr(strTest)) : 13F76C   Hex$(StrPtr(strTest)) : DD41924 : 10字节加字符串长度
      

  16.   

    哦 有点明白了可是的话,你是如何看出bB 的地址,分配了12F438~12F43B?
      

  17.   

    debug的结果,实际只能看出它的地址在12F43A啊?
      

  18.   

    这几个变量都是局部变量,分配在栈中。它在分配的时候,要进行边界对齐,32位系统中这些简单变量都是分配的4字节空间,占用空间的尾数都是0~3、4~7、8~B、C~F(刚才也看到我在1F中有个地方写错了:(iT 的地址,分配12F430~12F434,使用12F432~12F434) 应该是:(iT 的地址,分配12F430~12F433,使用12F432~12F43) )。
    对于占用不到4字节的变量,多余的空间都是空出不用的。
      

  19.   

    谢谢chen8013。有一点不明白,栈不是后进先出的么?先声明的变量地址应该靠前不对么?现在何以后声明的变量还在前面?
      

  20.   

    to chen8013: 21楼不问了,又观察了一哈,貌似明白了。呵呵。
      

  21.   

    但是新问题来了:12F43A      (bB 的地址,分配12F438~12F43B,使用12F43A) 为何bB不是使用12F43B而是使用12F43A?不是高字节对齐么?
      

  22.   

    这我也不太清楚了,应该跟硬件有很大的关系。现在的数据总线是32位的,内存←→CPU 之间的数据交换,每次肯定是4个字节。
    它选择在第三个字节上,可能在运算、分离数据上实现起来方便些吧。
      

  23.   


    x1=x xor y
    x = x1 xor y抱歉是我的理解错误,下面的代码测试一下就明白了
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal Destination As Long, ByVal Source As Long, ByVal Length As Long)Private Type MyStruct
      X As Byte
      y As Byte
      m As Integer
      z As Long
    End Type
    Private Sub Form_Load()
        Dim X As MyStruct
        Dim l As Long, m As Long
        l = 8888888
        
        X.X = 1
        X.y = 2
        X.z = 9999999
        
        '以非对齐结构取z的值到l
        m = VarPtr(X.X) + 2
        CopyMemory ByVal VarPtr(l), ByVal m, 4
        
        MsgBox l
        
        '以对齐结构取z的值取l
        m = VarPtr(X.X) + 4
        CopyMemory ByVal VarPtr(l), ByVal m, 4
        
        MsgBox l
    End Sub
      

  24.   

    谢谢chen0813。还想问一下,在1楼你写的“数组元素的存放应该是连续的,只是首址按双字对齐。”这句话是什么意思?谢谢unsigned。看来VB中的结构也是自动对齐的了。另外,还想问一下你在6楼的发言是什么意思?是说sizeof显示的仍是实际大小么?
      

  25.   

    再问unsigned: 奋特,我怎么就是弄不懂呢?我觉得这个函数的结果还是一个有符号常整形啊?Function UnsingedAdd(ByVal Start As Long, ByVal Incr As Long) As Long
        Const SignBit As Long = &H80000000
        UnsignedAdd = (Start Xor SignBit) + Incr Xor SignBit '这里明明只xor了一次啊?哪里做了“再把符合位替换回去”的操作?
    End Function
    ==以下是你在4楼的发言==
    4.最高位为符号位,当通过xor把符合位去掉时就可以把一个无符号大数缩小,然后就可以进行运算,最后再把符合位替换回去,就相当于,先减2147483648,然后进行计算,最的再加回去。这里需要满足的是两个条件: 
    1).结果不能溢出; 
    2).start为大于2147483648的一个无符号数 
    3).Incr是一个较小的正数
      

  26.   

    对于没有任何一元素有必要进行对齐处理的结构,其占用宽度不会被调整.
    typedef struct _tagA{
    char x;
    }A;
    typedef struct _tagB{
    A a;
    char y;
    }B;
    typedef struct _tagC{
    B b;
    int z;
    }C;
    typedef struct _tagD{
    C c;
    char t;
    }D;printf("sizeof(A)=%d,sizeof(B)=%d,sizeof(C)=%d,sizeof(D)=%d\r\n",sizeof(A),sizeof(B),sizeof(C),sizeof(D));
      

  27.   


    那么,如果像soyokaze在8楼说的那样,我用这个函数做指针的运算的话,还是不够用的吧?因为结果并不是真正的指针的值,是吧?
      

  28.   

    但是unsigned,奇怪的是,我在VB里测这个结构的长度却是6字节type MyStruct
      x as byte
      y as byte
      z as long
    End Type
    这说明在VB里结构没有按字长对齐啊?
      

  29.   

    这个函数就是用来运算32位的x86系统的地址(就是指针)的,因此不用担心。
    “结果是有符号的”,的确,VB对于32位Long数据都按照有符号数运算,这个反映在:
    1)运算结果小于-&H80000000和大于&H7FFFFFFF会溢出;
    2)MSB为1的,用Debug等会显示为负数。
    不过,这只是VB的编译器这么认为,其实一个32位数作为地址指针时,就是无符号的,因为这个地址需要传给API呀,C++库呀什么的,直接传过去,OK的。
    所以,这个函数只是借助VB的语法、规则和编译器完成了对一个32位数的加减法,并不在乎VB怎么解释这个数,只要保证结果正确即可。
    再多说一下,符号只是人们为了运算简便而认为规定的,在硬件实现上,不论有无符号的两个数,MCU都同样放到全加器里进行加法运算,结果一样(指1、0的组合)的,只是解释不同,就代表两个不同的数。
      

  30.   

    Len(MyStruct) = 6
    LenB(MyStruct) = 8VB的Len()和LenB()是有微妙的区别的。
    Len()返回表面上(怎么说好呢,就是VB包装过的吧)的长度,LenB()返回实际占的字节数还有一例:
    Dim s as string
    s = "hello"
    Debug.Print Len(s), LenB(s)结果为:
    5             10
      

  31.   

    to unsigned: 另外你在29楼举的例子,我觉得把结构改成type MyStruct
      x as byte
      y as byte
      z as long
    End Type更有说服力。这时候得到的结果也是一个溢出,一个是9999999说明,虽然结构变量占用的字节数用len去取是6字节,但是它的数据在存放的时候是按字长对齐的。
      

  32.   

    谢谢Soyokaze。to chen8013, 你在1楼写的:当然数组元素的存放应该是连续的,只是首址按双字对齐。 这句话是什么意思?
    to Soyokaze, 你在3楼写的:还有,涉及到结构中包含数组时,也要遵循这个原则。这个这个是和上面这句话是一个意思么?另外,想请Soyokaze帮看看2楼的回复(1),是否那种情况实际上不会出现?因为VB其实是按字长对齐的。
      

  33.   

    可以看这个例子,很能说明问题Option ExplicitPrivate Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)Private Type MyStruct
        x As Byte
        y As Byte
        z As Long
    End TypePrivate Sub Form_Load()
        Dim t As MyStruct
        With t
            .x = 12
            .y = 34
            .z = &H5678 '在内存中存放为 &H78 &H56 &H00 &H00
        End With
        
        Dim p As Long
        Dim b As Byte
        p = VarPtr(t)
        
        Call CopyMemory(b, ByVal p + 2, 1)
        Debug.Print Hex(b)
        
        Call CopyMemory(b, ByVal p + 4, 1)
        Debug.Print Hex(b)
    End Sub结果是:
    0
    78
      

  34.   


    “strTest实际内容的地址发生了变化,可见变长字符串运算是要重新分配内存的”
    这个的确这样,我遇到过。不光是变长字符串,包括用 Redim 声明的变长数组(包括非字串类型),重新Redim时,各元素地址也会发生变化。所以呀,VB的效率~~
      

  35.   

    奇怪,你在38楼给的例子,那个API我用unsigned的声明就溢出了,害得我的VBA死掉重启了。就是你都声明为anyDestination As Any, Source As Any,unsigned声明为long的那里问题是你都是byval传进去的,不是应该和声明为long一样的么
      

  36.   

    Call CopyMemory(b, ByVal p + 2, 1)这样是为了把P变量内的值+2之后的那个"值"传进去,而不是P这个变量的"址".所以要加上BYVAL,因为声明里默认是ByRef.当声明里面不是声明为ByRef as any而是ByVal as Long时,这个调用就可以是p+2了,可以省去ByVal说明.38楼代码使用ByVal as Long式声明的话,第一个参数b的值当时是0,向着0这个地址复制数据,会出错.
      

  37.   


    抱歉,我我我还是不理解。无限地羞愧……主要是对16进制加减法不太熟悉、对内存结构也不大了解……Function UnsingedAdd (ByVal Start As Long, ByVal Incr As Long) As Long
        Const SignBit As Long = &H80000000
        UnsignedAdd=(Start Xor SignBit) + Incr Xor SignBit
    End Function比如,假设start=H8FFFFFFE,incr=1,那么我希望的指针位置实际上是H8FFFFFFF
    用这个函数计算完得到的指针位置传到API里难道正确么?我怎么觉得算出来的是FFFFFFF,跟我想要的结果差的远啊?
      

  38.   

    函数返回的就是8FFFFFFF,没错。
      

  39.   

    8FFFFFFE Xor 80000000 = FFFFFFE
    1 Xor 80000000 = 80000001
    FFFFFFE + 80000001 = 8FFFFFFF
      

  40.   

    算出的结果是 &H8FFFFFFF(Start Xor SignBit) + Incr Xor SignBit :
    (Start Xor SignBit) ------> &H8FFFFFFE Xor &H80000000 = &H0FFFFFFE
    (Start Xor SignBit)+1 ----> &H0FFFFFFE + 1 = &H0FFFFFFF
    (Start Xor SignBit)+1 Xor SignBit ---> &H0FFFFFFF Xor &H80000000 = &H8FFFFFFF
      

  41.   

    晕.看花眼了!算成(Start Xor SignBit)+(1 Xor SignBit)了。不好意思。LS正解!
      

  42.   


    谢谢Chen and WangMu.这么说,“UnsignedAdd=(Start Xor SignBit) + Incr Xor SignBit ” 这一行等价于UnsignedAdd=((Start Xor SignBit) + Incr )Xor SignBit 也就是说,“xor”这个运算符的优先级是低于“+”的?
      

  43.   

    另外请chen看一下26楼,有句话不理解的说。
      

  44.   

    chen的意是数组的第一个元素的地址是双字对齐的,后续元素则不再按双字对齐,而是依次连续不间断地排放,中间没有填充空闲字节。
      

  45.   

    那个 UnsignedAdd 广为流传却是有错的,你可以用 UnsignedAdd(&H7FFFFFFF, &H80000000) 试试。用下面的,或者《高级 Visual Basic》提供的 UAdd()
    Private Function UnsignedAdd(ByVal lX As Long, ByVal lY As Long) As Long
        Dim lX4 As Long
        Dim lY4 As Long
        Dim lX8 As Long
        Dim lY8 As Long
        Dim lResult As Long    lX8 = lX And &H80000000
        lY8 = lY And &H80000000
        lX4 = lX And &H40000000
        lY4 = lY And &H40000000    lResult = (lX And &H3FFFFFFF) + (lY And &H3FFFFFFF)    If lX4 And lY4 Then
            lResult = lResult Xor &H80000000 Xor lX8 Xor lY8
        ElseIf lX4 Or lY4 Then
            If lResult And &H40000000 Then
                lResult = lResult Xor &HC0000000 Xor lX8 Xor lY8
            Else
                lResult = lResult Xor &H40000000 Xor lX8 Xor lY8
            End If
        Else
            lResult = lResult Xor lX8 Xor lY8
        End If    UnsignedAdd = lResult
    End Function
      

  46.   

    霍霍。幸好从来没用过这样的代码。
    个人愚见,若无十分必要,尽量不要在vb中折腾这些东西,用c、c++好了,直接了当。
      

  47.   


    楼很高
    如果是连续的内存,比如你说的结构数组,他的对齐是整体对齐,而不是分布式
    也就是说如果有 dim i() as type(2个字节结构) ,这种情况下并非所有元素都处于对齐位置上
    但也包含一个很特殊的情况,起始地址不为对齐地址上,但这种情况基本不会发生的,除非你特意安排了这样一个东西,否则系统那里申请过来的,是基本不会有这种的
    有点说乱了,总之就是,连续的内存,他的对齐是整体对齐,而不是象你说的每个元素都要对齐
      

  48.   

    引用 55 楼 slowgrace 的回复:
      
    那么,如果是结构数组,就每个数组元素都是32位对齐的吧? 
    =============
    是的。不必格外填充字节,就自然地对齐了。
      

  49.   

    其实关于地址对齐,需要搞清楚——是由谁进行的对齐。a)VB 设计的时代,16 位为主流,所以它用 2 字节对齐。
    b)现在的主流操作系统是 32 位的,它们用 4 字节对齐。1)变量的声明,用栈空间,编译器(VB)决定,所以 2 字节对齐。
    2)结构的成员,无论用什么空间,其内部划分编译器(VB)决定,所以 2 字节对齐。
    3)而动态内存的使用,通过操作系统在堆上分配空间,就用 4 字节对齐。
     所以看字符串的地址、动态数组的首个成员的地址(SAFEARRAY.pvData),16进制地址的末尾不是0就是8。
      

  50.   

    奇怪啊,zhao.那个函数不对是不对,可是的话,用你在54楼给的数代进去,怎么会溢出呢?我在立即窗口一步一步算都不溢出的
      

  51.   

    如果4字节对齐,strTest 就应该在 13F76A + 4 = 13F76E 处。
      

  52.   

    to zhao: 61楼的问题明白了,不问了。
      

  53.   

    谢谢。另外 取模运算 是不是等价于 对特定数的异或运算比如 与&h80000000异或 等价于 对2^32取模?
      

  54.   

    另外,你和unsigned对VB内存使用的一些经验是不是来自比较旧版本的VB,比如VB3/VB4/VB5只不过在VB6中这些悄悄地发生了改变?
      

  55.   

    但是你在60楼说的那些原则也许还是正确的就是:关于地址对齐,需要搞清楚——是由谁进行的对齐。 1)变量的声明,用栈空间,编译器(VB)决定。 
    2)结构的成员,无论用什么空间,其内部划分编译器(VB)决定。 
    3)而动态内存的使用,通过操作系统在堆上分配空间,由OS决定。 
      

  56.   


    我对LZ的这个问题,这么理解一下哈:
    60L所说的是有数据后的地址对齐,如果地址不对齐的话,当要再使用此变量中的数据时,就会取不出正确的来
    而LZ的意思只是说明了,在IDE中定义一个变量后,IDE或者OS如何来分配内存地址这个变量,二者说的不是同
    一东西.当你定义好一个变量,系统将按照一定的规定分配内存地址给这个变量,可能LZ不大好理解,我们举个例子来说
    明一下:
    Private Type DemoType
        strString As String
        byteByte() As Byte
    End TypePrivate Sub Form_Load()
        Dim Demo As DemoType
        Dim i As Integer
        
        ReDim byteByte(100)
        For i = 0 To 100
            byteByte(i) = ""            '这里赋字节类数据
        Next i
        Demo.strString = "This is string"
        
    End Sub那么所谓的地址对齐,就是指的上面的这个自定义类型DemoType
    在它里面有二个变量,分别是字符型和字节型,那么在给这个自定义类型赋完值后,在异机上面需要
    取得这个自定义类型中的数据,就必须注意对齐地址
      

  57.   

    上面写错了哈  :)
        ReDim Demo.byteByte(100)
        For i = 0 To 100
            Demo.byteByte(i) = ""            '这里赋字节类数据
        Next i
      

  58.   

    谢谢慷慨的vbman,俺有分了,哈哈,加分先!!
      

  59.   

    to soyokaze:我真太笨了。那个unsigned算法我还是不理解。tiger_zhao说这个算法有问题(54楼),我试了,确实会溢出。不过我看到tiger_zhao给出的算法也有类似的与&h80000000异或的语句。想来zhao给出的算法是在我抄来的那个算法的基础上改进的。最基本的思路估计差不多。可是,就是这个最基本的思路,我没办法理解。我不明白为嘛要异或来异或去。我试了几个数貌似用正常的加减都对的啊,嘛折腾一个特别的unsigned算法啊。UnsignedAdd = ((start Xor SignBit) + incr) Xor SignBit我感觉,上面这个语句简直要毁了我的五一节。你能不能举出一些可能会溢出的具体例子,以及这个算法是如何巧妙地避免了溢出、又得到了正确的结果?
      

  60.   

    参见我在8#的回复
    注意上面的Start <0只是站在VB的角度上,作为地址,Start一直是一个无符号数。
      

  61.   

    例1
    start = &H7fffffff
    incr = 1
    直接加会溢出,调用函数得&H80000000例2
    start = &H80000000
    incr = -1
    直接加会溢出,调用函数得&H7fffffff例3
    start = 3
    incr = -4 (&Hfffffffc)
    调用函数溢出例4
    start = &Hfffffffc (即带符号数-4)
    incr = 5
    调用函数溢出
      

  62.   

    总之,把start看作无符号数,调用时用“&H”的形式,如7写为&H00000007,以免混淆。
    incr可以是负数(代表指针自减),输入时可以采用诸如5、-6等形式。但要明确,对于负数,是以其补码存储到计算机里的(同时MSB为1)。例如-6,其实就是&HFFFFFFFA。
    然后这两个16进数相,按位全加。取其后面的32位作为结果。
    什么情况会溢出呢?两个符号相反的数相加不会溢出,对于两个符号相同的数,如果结果的bit31(即MSB)与bit32(进位位,不体现在结果中,硬件完成)相反(即一个是1,一个是0),溢出。
    所以,分析这个函数时,需要按我上面所说的把start与incr转换为16进制,这样函数具体怎样工作的,一目了然。
      

  63.   

    既然已经命名为 UnsignedAdd,Soyokaze 在 88 楼说传入负数做减法是具有严重二义性的,Incr := &H80000000 到底是 +2147483648 还是 -2147483648 就不确定了。
    既然做无符号运算,有 UnsignedAdd 就要有对应的 UnsignedSub。
      

  64.   

    To: slowgrace
    你的帖子那里分析什么补码是完全错误的。
    我们的目标用 UnsingedAdd() 进行无符号数的加法,其值域范围就是 UInt32 : &H00000000 - &HFFFFFFFF (0 - 4294967295)。
    而 VB 的 Long 是 Int32: &H80000000 - &H7FFFFFFF ((-2147483648) - 2147483647),可以用与 Unsigned 运算的只有 &H00000000 - &H7FFFFFFF (0 - 2147483647)。那么其最高位 &H80000000 (2147483648) 就不能参与用 VB.Long 的加法运算,必须自己处理,这就是 54 楼函数中要提取 lX8、lY8 的原因。
    而且次高位 &H40000000 (1073741824) 的相加会向最高位产生进位,在 VB.Long 的加法运算中就是溢出错误,必须自己处理,这就是 54 楼函数中要提取 lX4、lY4 的原因。
    至于剩余的位(54 楼函数中对 lX或lY 进行 And &H3FFFFFFF 操着后)进行相加,最多进位到次高位 &H40000000,完全可以用 VB.Long 进行加法运算。而所谓的自己处理,就是用 Xor 来模拟不进位的二进制加法防止溢出错误,其实就是在已知某个位的值时,通过 Xor 强制反转该位的值(0/1反转)。'注:以下注释中的加减运算都是无符号运算'
    Private Function UnsignedAdd(ByVal lX As Long, ByVal lY As Long) As Long
        Dim lX4 As Long
        Dim lY4 As Long
        Dim lX8 As Long
        Dim lY8 As Long
        Dim lResult As Long    '提取最高位'
        lX8 = lX And &H80000000
        lY8 = lY And &H80000000
        '提起次高位'
        lX4 = lX And &H40000000
        lY4 = lY And &H40000000
        '剩余位直接相加'
        lResult = (lX And &H3FFFFFFF) + (lY And &H3FFFFFFF)    If lX4 And lY4 Then '次高位同时为 1,和为 &H80000000'
            'lResult = lResult + 次高位和&H80000000 + X的最高位 + Y的最高位'
            '其中向更高位 &H100000000 的进位直接忽略了'
            lResult = lResult Xor &H80000000 Xor lX8 Xor lY8
        ElseIf lX4 Or lY4 Then '次高位只有一个 1'
            'lResult = lResult + 次高位和&H40000000 + X的最高位 + Y的最高位'
            If lResult And &H40000000 Then '剩余位的和向次高位有进位'
                '  lResult + 次高位和&H40000000'
                '= lResult + &H80000000 - &H40000000'
                '= lResult Xor &HC0000000 
                lResult = lResult Xor &HC0000000 Xor lX8 Xor lY8
            Else
                lResult = lResult Xor &H40000000 Xor lX8 Xor lY8
            End If
        Else '次高位全0,和为0'
            'lResult = lResult + 次高位和&H00000000 + X的最高位 + Y的最高位'
            lResult = lResult Xor lX8 Xor lY8
        End If    UnsignedAdd = lResult
    End Function
      

  65.   

    既然已经命名为 UnsignedAdd,Soyokaze 在 88 楼说传入负数做减法是具有严重二义性的,Incr := &H80000000 到底是 +2147483648 还是 -2147483648 就不确定了。
    既然做无符号运算,有 UnsignedAdd 就要有对应的 UnsignedSub。
      

  66.   

    还没完全看懂,先严重谢谢soyakaze and zhao。另外,异或真是个奇妙的运算符,跟它对眼中……
      

  67.   

    谢谢zhao,你注释得真细致。现在看来,这个代码本身思路也很清晰。只是我被那么多xor吓住了。另外
    (1)是不是有这种定论:对两个无符号整数按位异或,就等价于,这个两个无符号整数相加?
    (2)如果这个结论成立,这个结论是否可推及有符号整数的十六进制运算?
      

  68.   

    再想想,也许并不需要上面的定论。只要我脑子里装好那32个位(尤其是最高位、次高位),这个算法就很清晰和正确了。像soyokaza说的,只要我心里想着16进制的直接相加,它就是很容易理解的。想起,极限编程里提到的"整个系统都应该有个metaphor",有个大家都明白的隐喻,一切都会变得容易理解。这里这个算法的隐喻就是这一串01互相在加减,把它转换到十进制去思考那是彻底闹拧了。