函数调用过程中堆栈运行情况

    最近看到很多资料,都是关于堆栈的,忍不住想亲自对函数调用过程中的堆栈运行情况探个究竟,下面待我细细剖来!
    操作系统:windows xp
    编译器:vc 6.0 sp2
    程序编译版本:debug,注意:release版本生成的程序与debug版本相差甚大,但大致原理相同,因此release版本的堆栈运行情况不在本文关注范围内。

    首先看看《Computer Systems》一书中关于函数调用的描述:

    IA32 programs make use of the program stack to support procedure calls. The stack is used to pass procedure arguments, to store return information, to save registers for later restoration, and for local storage. The portion of the stack allocated for a single procedure call is called a stack frame. Figure 3.16 diagrams the general structure of a stack frame. The topmost stack frame is delimited by two pointers, with register %ebp serving as the frame pointer, and register %esp serving as the stack pointer. The stack pointer can move while the procedure is executing, and hence most information is accessed relative to the frame pointer.

    Suppose procedure P (the caller) calls procedure Q (the callee). The arguments to Q are contained within the stack frame for P. In addition, when P calls Q, the return address within P where the program should resume execution when it returns from Q is pushed on the stack, forming the end of P’s stack frame. The stack frame for Q starts with the saved value of the frame pointer (i.e., %ebp). followed by copies of any other saved register values.
    Procedure Q also uses the stack for any local variables that cannot be stored in registers. This can occur for the following reasons:
    1. There are not enough registers to hold all of the local data.
    2. Some of the local variables are arrays or structures and hence must be accessed by array or structure references.
    3. The address operator ‘&’ is applied to one of the local variables, and hence we must be able to generate an address for it.
    Finally, Q will use the stack frame for storing arguments to any procedures it calls.
    As described earlier, the stack grows toward lower addresses and the stack pointer %esp points to the top element of the stack. Data can be stored on and retrieved from the stack using the pushl and popl instructions. Space for data with no specified initial value can be allocated on the stack by simply decrementing the stack pointer by an appropriate amount. Similarly, space can be deallocated by incrementing the stack pointer.


    下面进入测试过程,我的测试代码如下:

    #include 
    void test(int i)
    {
          char output[8];
          strcpy(output, "abcdefg");
    }

    int main()
    {
          test(10); 
          return 0;
    }

H.323与SIP

    粗浅的认识:H.323和SIP同为语音通信协议,但H.323不仅考虑分组交换网,还考虑了PSTN等电路交换网,因此协议族本身包含了处理两者区别的内容,H.323协议族也被设计得很复杂,内容相当多。相比之下,SIP协议架构在分组交换网上,因此需要考虑的情况比较简单,协议也就比较简单。VOIP之初,电信网自身不太发达,PSTN占比重比较大,因此H.323协议较有市场,但是随着分组交换网的发展,尤其是NGN的发展,PSTN逐渐融合到分组交换网中,因此SIP开始以其简单、易于实现等优点显示出威力。

PCI总线资源分配过程分析——尚有疑问!

    杜工上个礼拜就说,我们的PCI驱动程序在台湾的工控机上装不了,这周又打来电话,我只好花时间研究了一下。
    首先了解一下PCI总线的资源枚举基础知识吧:
    计算机的接口卡一般会用到I/O端口、存储器空间、中断及DMA等计算机资源。传统ISA接口卡通过更改跳线来避免多块卡之间的资源冲突,PCI接口卡则摒弃了硬件跳线,由软件统筹分配资源,这被称为即插即用。为实现此功能,PCI协议除了可以对I/O空间、存储器空间读写外,还定义了对配置空间的读写(C/BE0~C/BE3=1010、1011)。所谓配置空间,是指映射到每块接口卡上的256字节的特殊功能寄存器。设计者事先在配置空间的指定位置写入需要申请使用的资源量,主板上电后,由PnP-Bios读取各卡的配置空间,对它们所需的资源进行统筹分配,再将分配结果写回对应的配置空间地址,完成自动配置。
    这段话提供了很重要的信息:PCI卡的资源分配动作,是由BIOS完成的。分配的实质,是将PCI的地址空间分配到PC机的虚拟地址空间,比如PCI设备有4M字节大小的存储器,那PC机肯定也要有4M的虚拟地址空间,否则PC机就访问不到PCI设备或者访问时跟别的设备冲突。
    PCI总线标准的配置寄存器中有6个BAR(Base Address Register),分别命名为BAR0~BAR5,6205只用到了3个,即:BAR0、BAR1、BAR2,6205复位时,它会为为这三个BAR置不同区段的地址(因为6205知道每个区段应该预留多大的空间,因此,它预置的值可以保证不重叠),PC机上电时,BIOS读入这三个地址区段,然后在PC机地址空间找出一段地址映射给三个空间,最后又把这个映射后的空间写回给6205的配置寄存器。而BAR3、BAR4、BAR5在6205复位时,都会被预置成0,因此,BIOS若要给他们都分配空间,必定会造成地址重叠,我看到的现象正是如此!
    在WINDOWS的设备管理器中,可以看到6205有三个地址区段都被映射到0x80000000-0x8fffffff,系统显示这三个区段资源冲突,板卡找不到足够的资源,因而不能工作。
    后来我用windriver看了所有的寄存器,与在设备管理器中看到的结果相同。反复重装了很多遍PCI驱动,仍然无效,只好狠心把driverstudio装了一遍,调试发现,PCI驱动只接收到了4个IRP包,而且全部都是IRP_MJ_PNP包,其中三个包的minor域是IRP_MN_QUERY_INTERFACE,另外一个是IRP_MN_QUERY_CAPABILITIES,非常关键的IRP_MN_START_DEVICE包没有出现,这说明操作系统根本不认为这个设备可以被启动。
    下午3点,我已经很疲惫了,我只能怀疑操作系统出问题了!
    征得杜工同意以后,下午重装了工控机的系统,再插上板卡,装驱动!居然就ok了!
    虽然花了1天时间,才把不是问题的问题找出来,但还是很有收获,至少我对PCI设备的资源何时分配这一点应该深信不疑了,但是疑问仍然存在,为什么那个被我格掉的系统会有资源冲突?按我上面的分析,的确应该有冲突才对,但为什么一般情况下又不会冲突?难道操作系统检查到冲突就把它忽略了?还是操作系统发现PCI设备的配置寄存器值为0,认为是非法值而不进行映射?

严重怀疑PLL电路引入的干扰

    事隔已久,当年做过的实验,如今已经很难一一记起。但重新翻阅6205的资料,仍然有新的认识,大概当时陷入了某一个点而不能自持,如今以局外人来看,也许会更清晰。
    以前我们总认为板子设计对PLL倍频产生了影响,但从没有想过PLL倍频电路也会对板子本身产生影响,PLL电路电源端的低通滤波器和环路滤波器的设计,应该引起我们的重视。可以按以下步骤检验:
    1. 测试数据总线上的波形
    2. 将晶振取下,再测试数据总线上的信号波形
    3. 对比波形,如果相差很大,那就证明了我的猜测

    另外还可以做的实验:使用50MHz的晶振,采用×4模式,也许会有所改观,因为以前的实验证明0被误成1的概率比1被误成0的概率低,而×4是‘001’,而×10是‘011’

    

暗恋PHP

    无意间看了一些PHP的东西,好简洁,好直接的语言,^_^,禁不住玩了几下。
    与python相比,从脚本语言的角度来看,PHP借助于web这个潜力无限的平台,发展得相当快,而python似乎只是在C、C++的基础上,提供了更简洁的语法、更强大的库、更直接的运行方式(解释型语言),但应用领域没有什么大的突破,难怪有人说python是个鸡肋。
    哪天把我派到多媒体组做网站算了,^_^。

貌似vhdl潜力可挖的地方很不少啊,_

不得不说,我对vhdl(或者说数字逻辑设计)完全是门外汉,^_^,以下两段程序:
代码片段1:
PWMcounterscreen.width/2)this.style.width=screen.width/2;>rocess(pclk, set_pwmprd_l, set_pwmhlev_l, reset_l)
   begin
      if pclkevent and pclk = 1 then
          if ((set_pwmprd_l = true_l) or (set_pwmhlev_l = true_l) or (reset_l = true_l)) then
              count_temp <= "000000000000001";
              PWM_ctrl <= 1;
          elsif (count_temp >= count_prd) then
              count_temp <= "000000000000001";
              PWM_ctrl <= 0;
          elsif (count_temp >= count_hlev) then
              count_temp <= count_temp + 1;
              PWM_ctrl <= 1;
          else
              count_temp <= count_temp + 1;
          end if;          
      end if;
   end process PWMcounter;

代码片段2:
PWMcounterscreen.width/2)this.style.width=screen.width/2;>rocess(pclk, set_pwmprd_l, set_pwmhlev_l, reset_l)
   begin
      if pclkevent and pclk = 1 then
          if ((set_pwmprd_l = true_l) or (set_pwmhlev_l = true_l) or (reset_l = true_l)) then
              count_temp <= count_prd;
              PWM_ctrl <= 0;
          elsif (count_temp <= "000000000000001") then 
              count_temp <= count_prd;
              PWM_ctrl <= 0;
          elsif (count_temp <= count_validwidth) then
              count_temp <= count_temp - 1;
              PWM_ctrl <= 1;
          else
              count_temp <= count_temp - 1;
          end if;          
      end if;
   end process PWMcounter;

这两段代码的差异仅仅在于一个是加法,另一个是减法,使用减法时,可以减少一次15bit变量的比较运算(变成了变量与常量的比较),但恰恰是这个判断,使得编译后的资源占有率从92%顿减到71%,编译时间都加快了好几倍,令我受宠若惊啊,^_^。
这段代码由于历史原因,一直使用加法,前几天想到减法可以减少一次15bit变量比较,但没有想到效果如此显著,看来以后要提高vhdl逻辑设计的水平啊,^_^

搞不清楚vhdl脾气

    今天搞了很久,就是希望把一个写signal的逻辑写进cpld,结果搞了很久都没有搞定,总是报资源不足,真搞不懂它的资源怎么分配的,资源不足时的代码如下(限于篇幅,这里只贴前后对比的代码):
pwm_set_procscreen.width/2)this.style.width=screen.width/2;>rocess(reset_l,pce1_l)
    begin
       if (reset_l = true_l) then
           count_prd(14 downto 7) <= "00001111";
           count_llev(14 downto 7) <= "00000111";
       elsif ((pce1_l = true_l) and (ah = dsp_reg_addr)) then 
           if (al = pwmprd_addr) then
               set_pwmprd_l <= true_l;
               count_prd(14 downto 7) <= gd(7 downto 0);
           elsif (al = pwmllev_addr) then
               set_pwmllev_l <= true_l;
               count_llev(14 downto 7) <= gd(7 downto 0);
           else
               set_pwmprd_l <= false_l;
               set_pwmllev_l <= false_l;
           end if;  
       end if;
end process pwm_set_proc;
这样的代码,一直都没有办法成功编译进cpld,后来,我查看了前面的代码,把一段写led的代码copy过来,稍加修改:
pwm_procscreen.width/2)this.style.width=screen.width/2;>rocess(pwe_l)
                      begin
                          if(pwe_levent and (pwe_l=false_l)) then
                              if((ah=dsp_reg_addr) and (al=pwmprd_addr)) then
                                  set_pwmllev_l <= true_l;
                                  count_prd(14 downto 7) <= gd(7 downto 0);
                              elsif((ah=dsp_reg_addr) and (al=pwmllev_addr)) then
                                  set_pwmprd_l <= true_l;
                                  count_llev(14 downto 7) <= gd(7 downto 0);                       
                              else
                                  set_pwmllev_l <= false_l;
                                  set_pwmprd_l <= false_l;                                  
                              end if;
                          end if;
                      end process;
这样就搞定了,可以编译进资源了,看来一个工程中,如果多个process采用类似的信号处理方法,会更易于综合。

银弹和我们的职业

编程是不是有前途的职业?30岁以后一定得转行?工作外包到越南怎么办? 新技术层出不穷,怎么才能跟上技术发展的趋势?工具越来越牛,以后编程像组装乐高积木怎么办?也许俺比较孤陋寡闻,看到的消极观点多。比如这里,还有这里。积极的观点少。像云风那样的铁杆编程迷更为罕见。嘿嘿,我没有答案。前天重读Frederick P. Brooks的《没有银弹》,有点感想而已。

《没有银弹》的中心思想是软件开发的困难分为两类。一类是暂时困难(accidental difficulty) ,另一类是本质困难(essential difficulty)。暂时困难可以通过技术的进步来解决。比如说检查句法错误就是暂时困难,写出绘制窗口的代码也是暂时困难。现代IDE基本解决了这些问题。而本质困难没有工具或技术可以消除。B老大争辩道,软件的本质是一堆互相作用的抽象结构:数据,算法,关系,函数调用。。。这些抽象结构应该尽量独立于表现它们的具体形式。所以说,软件编程的本质困难在于写出这些抽象结构的规范,设计这些抽象结构,和测试这些结构的正确性。注意哈。B老大说,如果上述判断正确,那么世上便没有银弹。幸好,到目前为止B老大的判断颠扑不破。

银弹和我们的职业发展有什么相干?很简单:我们得把时间用于学习解决本质困难。新技术给高手带来方便。菜鸟们却不用指望被新技术拯救。沿用以前的比喻,一流的摄影师不会因为相机的更新换代而丢掉饭碗,反而可能借助先进技术留下传世佳作。因为摄影的本质困难,还是摄影师的艺术感觉。热门技术也就等于相机。不停追新,学习这个框架,那个软件,好比成天钻研不同相机的说明书。而热门技术后的来龙去脉,才好比摄影技术。为什么推出这个框架?它解决了什么其它框架不能解决的问题?它在哪里适用?它在哪里不适用?它用了什么新的设计?它改进了哪些旧的设计?Why is forever. 和朋友聊天时提到Steve McConnell的《Professional Software Development》里面引了一个调查,说软件开发技术的半衰期20年。也就是说20年后我们现在知识里一半的东西过时。相当不坏。朋友打趣道:“应该说20年后IT界一半的技术过时,我们学的过时技术远远超过这个比例。具体到某人,很可能5年他就废了”。话虽悲观,但可见选择学习内容的重要性。学习本质技艺(技术迟早过时,技艺却常用长新)还有一好处,就是不用看着自己心爱的技术受到挑战的时候干嚎。C/C++过时就过时了呗,只要有其它的系统编程语言。Java倒了就倒了呗,未必我不能用.NET?Ruby昙花一现又如何。如果用得不爽,换到其它动态语言就是了。J2EE被废了又怎样?未必我们就做不出分布系统了?这里还举了更多的例子。

一句话,只有人是真正的银弹。职业发展的目标,就是把自己变成银弹。那时候,你就不再是人,而是人弹。

组合问题的解决方案——回溯法

       回溯法也称为试探法,该方法首先暂时放弃关于问题规模大小的限制,并将问题的候选解按某种顺序逐一枚举和检验。当发现当前候选解不可能是解时,就选择下一个候选解;倘若当前候选解除了还不满足问题规模要求外,满足所有其他要求时,继续扩大当前候选解的规模,并继续试探。如果当前候选解满足包括问题规模在内的所有要求时,该候选解就是问题的一个解。在回溯法中,放弃当前候选解,寻找下一个候选解的过程称为回溯。扩大当前候选解的规模,以继续试探的过程称为向前试探。

       回溯法的一般流程和技术

       在用回溯法求解有关问题的过程中,一般是一边建树,一边遍历该树。在回溯法中我们一般采用非递归方法。下面,我们给出回溯法的非递归算法的一般流程:

       在用回溯法求解问题,也即在遍历状态空间树的过程中,如果采用非递归方法,则我们一般要用到栈的数据结构。这时,不仅可以用栈来表示正在遍历的树的结点,而且可以很方便地表示建立孩子结点和回溯过程。

      【问题】 组合问题

       问题描述:找出从自然数1,2,…,n中任取r个数的所有组合。

       采用回溯法找问题的解,将找到的组合以从小到大顺序存于a[0],a[1],…,a[r-1]中,组合的元素满足以下性质:

       (1) a[i+1]>a[i],后一个数字比前一个大;

       (2) a[i]-i<=n-r+1。

       按回溯法的思想,找解过程可以叙述如下:

       首先放弃组合数个数为r的条件,候选组合从只有一个数字1开始。因该候选解满足除问题规模之外的全部条件,扩大其规模,并使其满足上述条件(1),候选组合改为1,2。继续这一过程,得到候选组合1,2,3。该候选解满足包括问题规模在内的全部条件,因而是一个解。在该解的基础上,选下一个候选解,因a[2]上的3调整为4,以及以后调整为5都满足问题的全部要求,得到解1,2,4和1,2,5。由于对5不能再作调整,就要从a[2]回溯到a[1],这时,a[1]=2,可以调整为3,并向前试探,得到解1,3,4。重复上述向前试探和向后回溯,直至要从a[0]再回溯时,说明已经找完问题的全部解。按上述思想写成程序如下:
[color=Red]
/* m代表可选数字个数,从1开始[1…m],r代表组合数字的个数 */
void combine3(int m, int r)
{
    int *a = new int[r];
    memset(a, 0, r * sizeof(int));
 
   
a[0] = 1;
    int i = 0;
 
    while (1)
    {
        // 达到规模,输出结果
        if (a[i] – i <= m - r + 1)
        {
         if (i >= r – 1)
         {
             for (int t = 0; t < r; t++)
             {
                 cout << *(a + t) << ;
             }
             cout << endl;
    
             a[i]++;
    
             continue;
         }
     }
  
     // 满足条件,因而扩大规模
     if (a[i] < m && i < r)
     {
         i++;
     }    
     else
     {
         if (i == 0)  // 退出
         {
             break;
         }
         else if (i > 0)
         { 
             a[i] = a[i – 1] + 1;
             a[–i]++;    // 回溯
         }   
     }

     // 组合数字肯定有序
     if (a[i] <= a[i - 1] && i > 0)
     {
         a[i] = a[i – 1] + 1;
     }
    }

    delete a;
}

int main()
{
    int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int b[8] = {0};
 
   
combine3(10, 2);
 
    return 0;
}
[color]

几种递归算法

1. 递归逆序打印字符串
void reverse(char *s)
{
     if(*s != �)
    {
        reverse(++s);
        putchar(*(s-1));  // 巧妙的利用栈的先进后出的特点!
    }
}

2. 递归方式将链表逆序
// p 为指向非空单链表中第一个结点的指针,本算法逆转链表并返回逆转后的头指针。基本思路是:如果链表中只有一个结点,则空操作,否则先逆转a2开始的链表,然后将 a1联接到逆转后的链表的表尾(即a2)之后。
LinkList reverse(LinkList p)
{
    if(p->next == NULL) return p;   // 链表中只有一个结点,逆转后的头指针不变
    else
    {
        q = p->next;          // q为链表(a2,…an)的头指针
        h = reverse(q);       // 逆转链表(a2,…an),并返回逆转后的头指针
        q->next = p;          // 将a1联接在a2之后
        p->next = NULL;

        return h;               // (a2,…,an)逆转表的头指针即为(a1,a2,…,an)
    }
}

3. 递归方式合并两个链表
Node * MergeRecursive(Node *head1 , Node *head2)
{
    if ( head1 == NULL )
        return head2 ;
    if ( head2 == NULL)
        return head1 ;

    Node *head = NULL ;
    if ( head1->data < head2->data )
    {
        head = head1 ;
        head->next = MergeRecursive(head1->next,head2);
    }
    else
    {
        head = head2 ;
        head->next = MergeRecursive(head1,head2->next);
    }

    return head ;
}