C++程序设计问题总结

最近这段时间写C++ code,感觉被咬的最惨的几个问题,1是类成员变量初始化问题;2是内存的释放问题;3是死循环与死锁问题。

1. 变量初始化,特别是类的原始数据类型成员变量。

对于类的类成员变量,无论如何编译器会保证它们的构造函数会被调用,并因此而得到适当的初始化。而那些原始数据类型的成员变量,如果遗漏了初始化的话,则很可能都具有一些难以预估的值。如果变量是指针类型的话,那就是野指针,我们会被它咬那几乎是难免的,SIGSEGV导致程序直接退出实在是让人蛋疼。而对于那些数据类型的成员变量,则会让大段大段的计算,由于某些值的无意义,而变得毫无意义。

成员初始化的另一个问题是类成员变量初始化的顺序。

这一点尽管比较基本,但还是被多次咬到。再被这个问题咬到,就剁手。。。

2.先来看一段code:

long long tm = SystemUtils::currentTimeMillis();
    int headLen = UDPMessage::tsDataHeadLength;
    int buflen = headLen + TARGET_PACKET_SIZE;
    char * buf = (char*) malloc(buflen);
    buf[0] = UDPMessage::verifyByte;
    buf[1] = UDPMessage::tsData;
    buf[2] = buf[3] = 0;
    uint32_t *p_channelId = (uint32_t *) &buf[4];
    uint32_t *p_tsId = (uint32_t *) &buf[8];
    uint32_t *p_offset = (uint32_t *) &buf[12];
    uint32_t *p_length = (uint32_t *) &buf[16];
    uint16_t *p_session = (uint16_t*) &buf[20];

    *p_channelId = poco_hton_32(_channelId);
    *p_tsId = poco_hton_32(_ts->idInt);
    *p_session = poco_hton_16(_sessionId);
    char* tar = buf + headLen;
    LOGI("send ts %u, tar = %p", *p_tsId, tar);

    uint64_t uploadStartTime = SystemUtils::currentTimeMillis();

    int targetSent = 0;
    int currentSpeed = 0;
    int sentSize = 0;
    int currentPos = _offset;
    //TODO speed control
    while (sentSize < _length && _stop == false) {
        memcpy(tar, _ts->_data + currentPos, targetSent);
        *p_offset = poco_hton_32(currentPos);
        targetSent = TARGET_PACKET_SIZE;
        if (currentPos + targetSent > _offset + _length) {
            targetSent = _offset + _length - currentPos;
        }
        *p_length = poco_hton_32(targetSent);

        _udpTransporter->sendMessage(buf, headLen + targetSent, _dst);
        currentPos += targetSent;
        sentSize += targetSent;

        Poco::Thread::current()->sleep(1);

        currentSpeed = sentSize * 1000 / (SystemUtils::currentTimeMillis() - tm);
        int rate = currentSpeed / uploadSpeed;
        rate = rate > 10 ? 10 : rate;
        if (rate > 1) {
            Poco::Thread::sleep(rate);
        }
    }
这一段code在执行的时候,经常会在while循环体的第一行,也就是memcpy的那一行发生段错误SIGSEGV。起初怀疑是 tar这个指针有问题,于是加了log将指针的值打印出来。某次在这个地方发生SIGSEGV时,tar的值为0x61E5 6DB8。但通过NDK来debug,发现发生SIGSEGV的地址明明是0x63AD E048。可以发觉两个地址相差的似乎相当的远。因而难免让人怀疑是memcpy()函数调用时,传递进去的length有问题。于是再来仔细检查长度 targetSent的各次更新。问题似乎也相当的明显,这个变量本来应该在更新之后才去用,而在此处竟然不小心写成了用完之后再更新了。于是有了改进后下面的版本:

long long tm = SystemUtils::currentTimeMillis();
    int headLen = UDPMessage::tsDataHeadLength;
    int buflen = headLen + TARGET_PACKET_SIZE;
    char * buf = (char*) malloc(buflen);
    buf[0] = UDPMessage::verifyByte;
    buf[1] = UDPMessage::tsData;
    buf[2] = buf[3] = 0;
    uint32_t *p_channelId = (uint32_t *) &buf[4];
    uint32_t *p_tsId = (uint32_t *) &buf[8];
    uint32_t *p_offset = (uint32_t *) &buf[12];
    uint32_t *p_length = (uint32_t *) &buf[16];
    uint16_t *p_session = (uint16_t*) &buf[20];

    *p_channelId = poco_hton_32(_channelId);
    *p_tsId = poco_hton_32(_ts->idInt);
    *p_session = poco_hton_16(_sessionId);
    char* tar = buf + headLen;
    LOGI("send ts %u, tar = %p", *p_tsId, tar);

    uint64_t uploadStartTime = SystemUtils::currentTimeMillis();

    int targetSent = 0;
    int currentSpeed = 0;
    int sentSize = 0;
    int currentPos = _offset;
    //TODO speed control
    while (sentSize < _length && _stop == false) {
        targetSent = TARGET_PACKET_SIZE;
        if (currentPos + targetSent > _offset + _length) {
            targetSent = _offset + _length - currentPos;
        }
        *p_length = poco_hton_32(targetSent);
        memcpy(tar, _ts->_data + currentPos, targetSent);
        *p_offset = poco_hton_32(currentPos);

        _udpTransporter->sendMessage(buf, headLen + targetSent, _dst);
        currentPos += targetSent;
        sentSize += targetSent;

        Poco::Thread::current()->sleep(1);

        currentSpeed = sentSize * 1000 / (SystemUtils::currentTimeMillis() - tm);
        int rate = currentSpeed / uploadSpeed;
        rate = rate > 10 ? 10 : rate;
        if (rate > 1) {
            Poco::Thread::sleep(rate);
        }
    }

3. STL的std::list容器,其back()函数返回该列表中的最后一个元素的引用。当列表中的元素为指针类型时,若该列表为空,back()函数返回的竟然不是NULL,而是1。比如下面的这段code:

TransportStream *lastTs = _tsList.back();
            if (lastTs != NULL && lastTs->mExcpectedTime != 0) {
                ts->mExcpectedTime = lastTs->mExcpectedTime + ((uint64_t) atof(lastTs->ts_dur.c_str()) * 1000);
            }
原本让人以为,做这样的检查,应该就已经比较可靠了。可谁知还是有发生SIGSEGV的风险。那好吧,就改为下面的这种写法吧:
if (_tsList.size() > 0) {
            TransportStream *lastTs = _tsList.back();
            if (lastTs != NULL && lastTs->mExcpectedTime != 0) {
                ts->mExcpectedTime = lastTs->mExcpectedTime + ((uint64_t) atof(lastTs->ts_dur.c_str()) * 1000);
            }
        }

4. Poco库的TaskManager,通过将一个Task的指针传递给它的start(),来执行一个Task。在Task的执行完成之后,TaskManager会自行将该Task的delete掉。因而,在Task::runTask()中,任何要return的地方都要注意这一点。Task的指针可能还保存在其他的列表中,或者其他什么地方。Task::runTask()返回之后,那些指针都将变成野指针,再次访问那些指针时,将会发生烦人的,难以追踪的SIGSEGV。

5. 死循环和死锁的行为非常相似。一不小心就写了一个死循环出来:

void TsTransportManager::dropAllTsUploadSession(uint32_t tsId) {
    P2pStatistics *p2pStatistics = P2pStatistics::getP2pStatistics();
    Poco::ScopedLock<Poco::FastMutex> lk(_uploadsessionListLock);
    for (TsUploadSessionList::iterator it = _uploadSessionList.begin(); it != _uploadSessionList.end(); ++it) {
        TsUploadSessionList::iterator curIt = it++;
        if ((*curIt)->_tsId == tsId) {
            replyTsError((*curIt)->_pkt);
            _uploadSessionList.erase(curIt);
            p2pStatistics->p2pDropTsRequest();
        }
    }
}

就这个循环的写法而言,其实每一次循环,都会对循环计数器,即it迭代器加2次。考虑迭代器it遍历到了列表的最后一个元素的情况:进入循环的时候it被加了一次,这个时候it的值应该为_uploadSessionList.end()了,然后在循环体的结尾处,又被加了一次,于是it将跨过_uploadSessionList.end(),由此则循环将永远不会结束

补充一点关于这个循环的写法的说明,即为什么要在进入循环体的开始处,就对it做递增操作?我们看到这个循环的循环体中,会做一些判断,判断为真是,会需要将元素给移除出列表。这样的写法是为了安全,及避免遍历的时候遗漏掉元素。

待续。。。

转载于:https://my.oschina.net/wolfcs/blog/406492

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值