zoukankan      html  css  js  c++  java
  • 切勿使用:指向局部变量的指针作为函数的返回指针!

    今天码代码的时候,出现了一个诡异的问题:

    首先:函数 pkt_analyzer 返回了一个 PktUnit类型的指针。我先把端点跑到puu赋值后的下一句,查看puu里面的内容,发现是正确的: payload_len = 7,pkt_len = 35

    接着我再向下跑一步,发现puu内容就不对了: payload_len = 1514280713 ;pkt_len = 17 整个就不对了……

    找了一个多小时,没发现问题出在哪儿!后来突然想到,可能是函数返回的时候出的问题,不能光看函数的立即返回结果!

    果然,这是一个属于:使用指向局部变量的指针作为函数返回值的例子!

     pointer * Func{

      return pointer = & iTemp;

    }

    这样做会造成非常严重的后果!!!!

    千万不要企图返回局变量堆栈上的指针,返回局部栈上的指针,你的指针就指向了该局部变量iTemp的地址,由于局部栈会在函数返回的时刻清栈,然而你的指针所指向的地址还是不变的。也就是说 pointer还是指向iTemp原来的那块内存,但是接着执行下来,那块内存的内容完全是不确定的!所以,导致你下次使用 函数返回指针 ret_pointer = Func(); ret_pointer的内容完全不确定,会产生灾难性后果!!

    先给出问题代码:

    #include "pkt_analyzer.h"
    #include "stdio.h"
    #include "string.h"
    #include "malloc.h"
    
    extern PktUnit *pkt_analyzer(char *rx_buffer, int rx_len);
                                           
    int main()
    {
        int i = 0;
        int payload_len = 0;
        u_int8 *my_payload = (u_int8 *)malloc(sizeof(u_int8));
    
        PktUnit *ppu = 0;
    
        char in_buffer[PKT_HDEADER_LEN + 7] = {0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0e,0x05,0x0d,0x2e,0x23,0x01,0xc2,0x0e,0x0be,0xff,0x03,0x04,0x05,0x06,0x07};
    
        ppu = pkt_analyzer(in_buffer,sizeof(in_buffer));
    
        printf("payload_len = %d
    ",ppu->payload_len);
        printf("pkt_len = %d 
    ",ppu->pkt_len);
        printf("timestampe = %ul",ppu->pkt_hdr.timestamp);
    
        memcpy(my_payload, ppu->payload,sizeof(u_int8)*ppu->payload_len);
    
        for(i=0; i< ppu->payload_len ;i++){
            printf("%x
    ", *my_payload++);
        }
    
        free(my_payload);
        return 0;
    }
    /*
     * recv_pkt_analyzer.c
     *
     *  Created on: 2014-5-7
     *      Author: fang ying
     */
    
    /* @Function Description: Perform packet analysis
     *
     * @param
     * @param
     * @return
     */
    
    #include "pkt_analyzer.h"
    #include "stdlib.h"
    #include "string.h"
    
    #ifndef NULL
    #define NULL 0
    #endif
    
    #define MakeDoubleWord(a,b,c,d,e,f,g,h)        (((u_int64)(a & 0xff) << 56 )|((u_int64)(b & 0xff) << 48)|
                                                   ((u_int64)(c & 0xff) << 40 )|((u_int64)(d & 0xff) << 32)|
                                                   ((u_int64)(e & 0xff) << 24 )|((u_int64)(f & 0xff) << 16)|
                                                   ((u_int64)(g & 0xff) << 8  )|((u_int64)(h & 0xff) << 0 ))
    
    #define MakeWord(a,b,c,d)                    (((u_int32)(a & 0xff) << 24 )|((u_int32)(b & 0xff) << 16)|
                                                   ((u_int32)(c & 0xff) << 8 ) |((u_int32)(d & 0xff) << 0))
    
    
    
    PktUnit *pkt_analyzer(char *rx_buffer, int rx_len){
    
        u_int32         get_class_id = 0;
        u_int32         get_cmd_field = 0;
        u_int64            get_timestamp = 0;
        u_int32            get_parameter = 0;
        u_int32         get_pkt_payload = 0;
    
        PktUnit    packet_rx;
        PktUnit *pkt_unit = NULL;
    
        /* first check the validity of rx buffer */
        if(rx_buffer == NULL || rx_len <= 0){
            return NULL;
        }
    
        get_class_id = MakeWord(rx_buffer[0],rx_buffer[1],rx_buffer[2],rx_buffer[3]);
        /* get the class identification of the rx packet */
        switch(get_class_id){
            case CMD_DATA:
                packet_rx.pkt_hdr.class_id = CMD_DATA;
                break;
            case SHORT_MSG:
                packet_rx.pkt_hdr.class_id = SHORT_MSG;
                break;
            case LONG_MSG:
                packet_rx.pkt_hdr.class_id = LONG_MSG;
                break;
            case EXTDATA_MSG:
                packet_rx.pkt_hdr.class_id = EXTDATA_MSG;
                break;
            case CMD_EXCHANGE:
                packet_rx.pkt_hdr.class_id = CMD_EXCHANGE;
                break;
            default:
                return NULL;
        }
    
        /* check direction */
        get_cmd_field = MakeWord(rx_buffer[8],rx_buffer[9],rx_buffer[10],rx_buffer[11]);
        if(get_cmd_field != MASTER_2_TASK){
            return NULL;
        }else{
            // fill the cmd field
            packet_rx.pkt_hdr.cmd_field = get_cmd_field;
        }
    
        /* get parameter field */
        get_parameter = MakeWord(rx_buffer[12],rx_buffer[13],rx_buffer[14],rx_buffer[15]);
        packet_rx.pkt_hdr.para_field = get_parameter;
    
        /* get timestamp information */
        get_timestamp = MakeDoubleWord(rx_buffer[20],rx_buffer[21],rx_buffer[22],rx_buffer[23],
                                    rx_buffer[24],rx_buffer[25],rx_buffer[26],rx_buffer[27]);
        packet_rx.pkt_hdr.timestamp = get_timestamp;
    
        /* get packet length */
        packet_rx.pkt_len = rx_len;
    
        /* analysis packet according to class id */
        if(get_class_id == CMD_DATA){
            packet_rx.payload_len = CMD_DATA_PAYLOAD_LEN;
        }
    
        if(get_class_id == SHORT_MSG){
            switch(get_parameter){
                case AUDIO_250MS:
                    packet_rx.payload_len = AUDIO_250MS_PAYLOAD_LEN;
                    break;
                case SHORT_MSG_250MS:
                    packet_rx.payload_len = SHORT_MSG_250MS_PAYLOAD_LEN;
                    break;
                case SHORT_MSG_500MS:
                    packet_rx.payload_len = SHORT_MSG_500MS_PAYLOAD_LEN;
                    break;
                case KEYBOARD_250MS:
                    packet_rx.payload_len = KEYBOARD_250MS_PAYLOAD_LEN;
                    break;
                case KEYBOARD_500MS:
                    packet_rx.payload_len = KEYBOARD_500MS_PAYLOAD_LEN;
                    break;
                default:
                    return NULL;
            }
        }
    
        if(get_class_id == LONG_MSG){
            switch(get_parameter){
                case LONG_MSG_500MS:
                    packet_rx.payload_len = LONG_MSG_500MS_PAYLOAD_LEN;
                    break;
                case LONG_MSG_750MS:
                    packet_rx.payload_len = LONG_MSG_750MS_PAYLOAD_LEN;
                    break;
                default:
                    return NULL;
            }
        }
    
        if(get_class_id == EXTDATA_MSG){
            switch(get_parameter){
                case EXT_DATA_1P2K:
                    packet_rx.payload_len = EXT_DATA_1P2K_PAYLOAD_LEN;
                    break;
                case EXT_DATA_2P4K:
                    packet_rx.payload_len = EXT_DATA_2P4K_PAYLOAD_LEN;
                    break;
                case EXT_DATA_4P8K:
                    packet_rx.payload_len = EXT_DATA_4P8K_PAYLOAD_LEN;
                    break;
                case EXT_DATA_9P6K:
                    packet_rx.payload_len = EXT_DATA_9P6K_PAYLOAD_LEN;
                    break;
                case EXT_DATA_19P2K:
                    packet_rx.payload_len = EXT_DATA_19P2K_PAYLOAD_LEN;
                    break;
                case EXT_DATA_25P6K:
                    packet_rx.payload_len = EXT_DATA_25P6K_PAYLOAD_LEN;
                    break;
                case EXT_DATA_38P4K:
                    packet_rx.payload_len = EXT_DATA_38P4K_PAYLOAD_LEN;
                    break;
                default:
                    return NULL;
            }
        }
    
        if(get_class_id == CMD_EXCHANGE){
            switch(get_parameter){
                case SIGNALING_400MS_RECV_MODE:
                    break;
                case AUDIO_250MS_RECV_MODE:
                    break;
                case SHORT_MSG_250MS_RECV_MODE:
                    break;
                case SHORT_MSG_500MS_RECV_MODE:
                    break;
                case KEYBOARD_250MS_RECV_MODE:
                    break;
                case KEYBOARD_500MS_RECV_MODE:
                    break;
                case LONG_MSG_500MS_RECV_MODE:
                    break;
                case LONG_MSG_750MS_RECV_MODE:
                    break;
                case EXT_DATA_1P2K_RECV_MODE:
                    break;
                case EXT_DATA_2P4K_RECV_MODE:
                    break;
                case EXT_DATA_4P8K_RECV_MODE:
                    break;
                case EXT_DATA_9P6K_RECV_MODE:
                    break;
                case EXT_DATA_19P2K_RECV_MODE:
                    break;
                case EXT_DATA_25P6K_RECV_MODE:
                    break;
                case EXT_DATA_38P4K_RECV_MODE:
                    break;
                default:
                    return NULL;
            }
        }
    
        /* get payload */
        packet_rx.payload = (int_8 *)malloc(sizeof(u_int8));
        //packet_rx.payload = &rx_buffer[PKT_HDEADER_LEN];
        memcpy(packet_rx.payload,&rx_buffer[PKT_HDEADER_LEN],sizeof(u_int8)*packet_rx.payload_len);
        
        pkt_unit = &packet_rx;
        return (pkt_unit);
    }
    /*
     * pkt_analyzer.h
     *
     *  Created on: 2014-5-7
     *      Author: fang ying
     */
    
    #ifndef _PKT_ANALYZER_H_
    #define _PKT_ANALYZER_H_
    
    /* type defination */
    typedef    char                int_8;
    typedef unsigned char         u_int8;
    typedef unsigned short        u_int16;
    typedef unsigned int        u_int32;
    typedef unsigned long long  u_int64;
    
    
    typedef struct PktHeader_t{
        u_int32    class_id;        /* class identification unique number*/
        u_int32    board_id;        /* board identification */
        u_int32 cmd_field;        /* command field */
        u_int32 para_field;        /* parameter field */
        u_int64    freq_info;        /* frequency information */
        u_int32 channel_SNR;    /* SNR of the channel */
        u_int64 timestamp;        /* timestamp */
    
    }PktHeader;
    
    
    typedef struct PktUnit_t{
        PktHeader pkt_hdr;        /* packet header */
        int_8       *payload;        /* data payload */
        u_int32   payload_len;    /* packet payload length */
        u_int32   pkt_len;        /* packet total length */
    }PktUnit;
    
    /* constriant definations */
    #define PKT_HDEADER_LEN        28
    
    /* class identification */
    #define CMD_DATA             0x1        /* command data unit */
    #define SHORT_MSG            0x2        /* short message data unit */
    #define LONG_MSG            0x3        /* long  message data unit */
    #define    EXTDATA_MSG            0x4        /* extra data frame unit   */
    #define CMD_EXCHANGE        0x5        /* command exchange unit   */
    
    /* board identification */
    #define MASTER_BOARD        0x0
    #define TASK_BOARD            0x1
    
    /* command field of command data unit */
    #define MASTER_2_TASK        0x1
    #define    TASK_2_MASTER        0x2
    
    /******************** command data unit header explain **********************/
    /* parameter field of command data unit */
    #define FREQUENCY_PAYLOAD_INFO        0x1
    #define    FREQUENCY_INFO_ONLY            0x2
    #define    PAYLOAD_INFO_ONLY            0x3
    #define    RESERVED                    0x4
    
    #define SYN_CHECKED                    0x1
    #define    RECEIVE_COMPLETE            0x2
    #define    EARLY_TX_SUCESS                0x3
    #define    EARLY_TX_FAILED                0x4
    
    /******************** short and long message data unit header explain ****************/
    /* parameter field of short message interact unit */
    #define AUDIO_250MS            0x1
    #define    SHORT_MSG_250MS        0x2
    #define    SHORT_MSG_500MS        0x3
    #define    KEYBOARD_250MS        0x4
    #define    KEYBOARD_500MS        0x5
    #define    LONG_MSG_500MS        0x6
    #define    LONG_MSG_750MS        0x7
    #define    EXT_DATA_1P2K        0x8
    #define    EXT_DATA_2P4K        0x9
    #define    EXT_DATA_4P8K        0x10
    #define    EXT_DATA_9P6K        0x11
    #define    EXT_DATA_19P2K        0x12
    #define    EXT_DATA_25P6K        0x13
    #define    EXT_DATA_38P4K        0x14
    
    /* length constraints of message */
    #define CMD_DATA_PAYLOAD_LEN                    7
    #define AUDIO_250MS_PAYLOAD_LEN                    62
    #define    SHORT_MSG_250MS_PAYLOAD_LEN                94
    #define    SHORT_MSG_500MS_PAYLOAD_LEN                94
    #define    KEYBOARD_250MS_PAYLOAD_LEN                94
    #define KEYBOARD_500MS_PAYLOAD_LEN                94
    #define LONG_MSG_500MS_PAYLOAD_LEN                478
    #define LONG_MSG_750MS_PAYLOAD_LEN                766
    #define    EXT_DATA_1P2K_PAYLOAD_LEN                574
    #define EXT_DATA_2P4K_PAYLOAD_LEN                574
    #define EXT_DATA_4P8K_PAYLOAD_LEN                574
    #define EXT_DATA_9P6K_PAYLOAD_LEN                1152
    #define EXT_DATA_19P2K_PAYLOAD_LEN                2304
    #define EXT_DATA_25P6K_PAYLOAD_LEN                3456
    #define EXT_DATA_38P4K_PAYLOAD_LEN                4608
    
    /**************** command exchange unit header explain *****************/
    /* parameter field of command exchange unit */
    #define    SIGNALING_400MS_RECV_MODE                0x0
    #define    AUDIO_250MS_RECV_MODE                    0x1
    #define    SHORT_MSG_250MS_RECV_MODE                0x2
    #define    SHORT_MSG_500MS_RECV_MODE                0x3
    #define    KEYBOARD_250MS_RECV_MODE                0x4
    #define    KEYBOARD_500MS_RECV_MODE                0x5
    #define    LONG_MSG_500MS_RECV_MODE                0x6
    #define    LONG_MSG_750MS_RECV_MODE                0x7
    #define    EXT_DATA_1P2K_RECV_MODE                    0x8
    #define   EXT_DATA_2P4K_RECV_MODE                    0x9
    #define EXT_DATA_4P8K_RECV_MODE                    0x10
    #define EXT_DATA_9P6K_RECV_MODE                    0x11
    #define EXT_DATA_19P2K_RECV_MODE                0x12
    #define EXT_DATA_25P6K_RECV_MODE                0x13
    #define EXT_DATA_38P4K_RECV_MODE                0x14
    
    #endif /* PKT_ANALYZER_H_ */

    问题出在这个地方:

    这里使用了指向局部变量的指针,并作为函数的返回指针。

    改正后的代码如下

    /*
     * recv_pkt_analyzer.c
     *
     *  Created on: 2014-5-7
     *      Author: fang ying
     */
    
    /* @Function Description: Perform packet analysis
     *
     * @param
     * @param
     * @return
     */
    
    #include "pkt_analyzer.h"
    #include "stdlib.h"
    #include "string.h"
    
    #ifndef NULL
    #define NULL 0
    #endif
    
    #define MakeDoubleWord(a,b,c,d,e,f,g,h)        (((u_int64)(a & 0xff) << 56 )|((u_int64)(b & 0xff) << 48)|
                                                   ((u_int64)(c & 0xff) << 40 )|((u_int64)(d & 0xff) << 32)|
                                                   ((u_int64)(e & 0xff) << 24 )|((u_int64)(f & 0xff) << 16)|
                                                   ((u_int64)(g & 0xff) << 8  )|((u_int64)(h & 0xff) << 0 ))
    
    #define MakeWord(a,b,c,d)                    (((u_int32)(a & 0xff) << 24 )|((u_int32)(b & 0xff) << 16)|
                                                   ((u_int32)(c & 0xff) << 8 ) |((u_int32)(d & 0xff) << 0))
    
    
    
    PktUnit *pkt_analyzer(char *rx_buffer, int rx_len){
    
        u_int32         get_class_id = 0;
        u_int32         get_cmd_field = 0;
        u_int64            get_timestamp = 0;
        u_int32            get_parameter = 0;
        u_int32         get_pkt_payload = 0;
    
        PktUnit    *packet_rx = (PktUnit *)malloc(sizeof(PktUnit));
    
        /* first check the validity of rx buffer */
        if(rx_buffer == NULL || rx_len <= 0){
            return NULL;
        }
    
        get_class_id = MakeWord(rx_buffer[0],rx_buffer[1],rx_buffer[2],rx_buffer[3]);
        /* get the class identification of the rx packet */
        switch(get_class_id){
            case CMD_DATA:
                packet_rx->pkt_hdr.class_id = CMD_DATA;
                break;
            case SHORT_MSG:
                packet_rx->pkt_hdr.class_id = SHORT_MSG;
                break;
            case LONG_MSG:
                packet_rx->pkt_hdr.class_id = LONG_MSG;
                break;
            case EXTDATA_MSG:
                packet_rx->pkt_hdr.class_id = EXTDATA_MSG;
                break;
            case CMD_EXCHANGE:
                packet_rx->pkt_hdr.class_id = CMD_EXCHANGE;
                break;
            default:
                return NULL;
        }
    
        /* check direction */
        get_cmd_field = MakeWord(rx_buffer[8],rx_buffer[9],rx_buffer[10],rx_buffer[11]);
        if(get_cmd_field != MASTER_2_TASK){
            return NULL;
        }else{
            // fill the cmd field
            packet_rx->pkt_hdr.cmd_field = get_cmd_field;
        }
    
        /* get parameter field */
        get_parameter = MakeWord(rx_buffer[12],rx_buffer[13],rx_buffer[14],rx_buffer[15]);
        packet_rx->pkt_hdr.para_field = get_parameter;
    
        /* get timestamp information */
        get_timestamp = MakeDoubleWord(rx_buffer[20],rx_buffer[21],rx_buffer[22],rx_buffer[23],
                                    rx_buffer[24],rx_buffer[25],rx_buffer[26],rx_buffer[27]);
        packet_rx->pkt_hdr.timestamp = get_timestamp;
    
        /* get packet length */
        packet_rx->pkt_len = rx_len;
    
        /* analysis packet according to class id */
        if(get_class_id == CMD_DATA){
            packet_rx->payload_len = CMD_DATA_PAYLOAD_LEN;
        }
    
        if(get_class_id == SHORT_MSG){
            switch(get_parameter){
                case AUDIO_250MS:
                    packet_rx->payload_len = AUDIO_250MS_PAYLOAD_LEN;
                    break;
                case SHORT_MSG_250MS:
                    packet_rx->payload_len = SHORT_MSG_250MS_PAYLOAD_LEN;
                    break;
                case SHORT_MSG_500MS:
                    packet_rx->payload_len = SHORT_MSG_500MS_PAYLOAD_LEN;
                    break;
                case KEYBOARD_250MS:
                    packet_rx->payload_len = KEYBOARD_250MS_PAYLOAD_LEN;
                    break;
                case KEYBOARD_500MS:
                    packet_rx->payload_len = KEYBOARD_500MS_PAYLOAD_LEN;
                    break;
                default:
                    return NULL;
            }
        }
    
        if(get_class_id == LONG_MSG){
            switch(get_parameter){
                case LONG_MSG_500MS:
                    packet_rx->payload_len = LONG_MSG_500MS_PAYLOAD_LEN;
                    break;
                case LONG_MSG_750MS:
                    packet_rx->payload_len = LONG_MSG_750MS_PAYLOAD_LEN;
                    break;
                default:
                    return NULL;
            }
        }
    
        if(get_class_id == EXTDATA_MSG){
            switch(get_parameter){
                case EXT_DATA_1P2K:
                    packet_rx->payload_len = EXT_DATA_1P2K_PAYLOAD_LEN;
                    break;
                case EXT_DATA_2P4K:
                    packet_rx->payload_len = EXT_DATA_2P4K_PAYLOAD_LEN;
                    break;
                case EXT_DATA_4P8K:
                    packet_rx->payload_len = EXT_DATA_4P8K_PAYLOAD_LEN;
                    break;
                case EXT_DATA_9P6K:
                    packet_rx->payload_len = EXT_DATA_9P6K_PAYLOAD_LEN;
                    break;
                case EXT_DATA_19P2K:
                    packet_rx->payload_len = EXT_DATA_19P2K_PAYLOAD_LEN;
                    break;
                case EXT_DATA_25P6K:
                    packet_rx->payload_len = EXT_DATA_25P6K_PAYLOAD_LEN;
                    break;
                case EXT_DATA_38P4K:
                    packet_rx->payload_len = EXT_DATA_38P4K_PAYLOAD_LEN;
                    break;
                default:
                    return NULL;
            }
        }
    
        if(get_class_id == CMD_EXCHANGE){
            switch(get_parameter){
                case SIGNALING_400MS_RECV_MODE:
                    break;
                case AUDIO_250MS_RECV_MODE:
                    break;
                case SHORT_MSG_250MS_RECV_MODE:
                    break;
                case SHORT_MSG_500MS_RECV_MODE:
                    break;
                case KEYBOARD_250MS_RECV_MODE:
                    break;
                case KEYBOARD_500MS_RECV_MODE:
                    break;
                case LONG_MSG_500MS_RECV_MODE:
                    break;
                case LONG_MSG_750MS_RECV_MODE:
                    break;
                case EXT_DATA_1P2K_RECV_MODE:
                    break;
                case EXT_DATA_2P4K_RECV_MODE:
                    break;
                case EXT_DATA_4P8K_RECV_MODE:
                    break;
                case EXT_DATA_9P6K_RECV_MODE:
                    break;
                case EXT_DATA_19P2K_RECV_MODE:
                    break;
                case EXT_DATA_25P6K_RECV_MODE:
                    break;
                case EXT_DATA_38P4K_RECV_MODE:
                    break;
                default:
                    return NULL;
            }
        }
    
            /* get payload */
        packet_rx->payload = (int_8 *)malloc(sizeof(u_int8));
        //packet_rx.payload = &rx_buffer[PKT_HDEADER_LEN];
        memcpy(packet_rx->payload,&rx_buffer[PKT_HDEADER_LEN],sizeof(u_int8)*packet_rx->payload_len);
        return packet_rx;
    }

    上面修改的地方,我定义了一个指针,并且动态申请了空间后面就对了。其思想是基于:

    因为函数中的局部变量在函数调用结束后就会被释放;这句话是对的,局部变量超出其作用域后就会被释放掉
    所以如果你在函数内部定义一个指针,并申请了空间;这句不怎么对,因为只查动态申请的内存都是在堆中申请,不会被释放掉

    但是,如果是使用字符串指针形式,char *str = "hello world"这个叫字符串字面常量。储存在静态区,是只读!!!!的所以可以返回

    #include<stdio.h>
     
    char    *returnstr()
    {
            char    *str="Hello!World!";
            printf("In returnstr, addr of str:	%p
    ", str);
            return  str; //"Hello!world!
    ";
    }
    int     main()
    {
            char *str=returnstr();
            printf("In main,      addr of str:	%p
    ", str);
            printf("%s
    ", str);
            return  0;
    }

     函数中的变量存储空间是在栈上分配的,函数结束时自动释放,所以不能返回这样的变量,即使你返回了,就像7楼所说的,也得到了正确的值,请不要高兴,这是因为那块内存还没有被占用,值还没有改变,所以你得到了正确的值,有一种情况是可以返回局部指针的,就是在堆上动态分配存储空间,这些空间在函数结束时不会自动释放,但是需要自己释放,所以得记住,要是忘记了就漏掉了。

  • 相关阅读:
    C#面向对象(二)之抽象类实现多态
    JavaWeb 学习0010-今日问题 2016-12-3
    JavaWeb 学习008-今日问题(非空验证尚未解决) 2016-12-2
    JavaWeb 学习007-4个页面,5条sql语句(添加、查看、修改、删除)2016-12-2
    JavaWeb 学习006-4个页面,5条sql语句(添加、查看、修改、删除)
    JavaWeb 学习005-4个页面,5条sql语句(添加、查看、修改、删除)
    JavaWeb 学习004-增删改查的编写
    JavaWeb 学习003-简单登录页面功能实现
    JavaWeb 学习001-登录页面-Servlet
    JavaWeb 学习001-登录页面
  • 原文地址:https://www.cnblogs.com/fangying7/p/3716832.html
Copyright © 2011-2022 走看看