美文网首页
C++后端开发的踩坑整理

C++后端开发的踩坑整理

作者: DayDayUpppppp | 来源:发表于2019-03-31 20:43 被阅读0次

C++开发的一些经验和踩坑整理

STL相关的坑

1. std::sort()函数要求严格弱序

STL文档中要求sort实现的函数要遵守严格弱序的规则。使用的时候,稍有不慎就会可能忘记,程序跑起来的时候,会导致容器越界,从而使程序coredump。google搜了一下,上面无数的人在这个问题上掉坑,stl的bug feature。

提供一个coredump的案例,以下是一个反例,可以看出来是为什么么?

struct TEST_STRUCT_INFO
{
    int id1; 
    int id2;
    int id3;  
};

bool _vec_sort_cmp(const TEST_STRUCT_INFO& item1
        , const TEST_STRUCT_INFO& item2)
{
    return item1.id1 <= item2.id1;
}

int main()
{
    TEST_STRUCT_INFO item = {1,2,3};
    vector<TEST_STRUCT_INFO> vec;
    for (int i = 0; i < 50; i++)
    {
         vec.push_back(item);
    }
    std::sort(vec.begin(), vec.end(), _vec_sort_cmp);
    return 0;
}

--------
$ ./a.out 
[1]    3151 segmentation fault (core dumped)  ./a.out
2. std::list中的size方法实现是O(n)的

C++11之前的std::list中的size方法实现是O(n)的。如果list中的元素特别巨大,而且要频繁获得size的时候,要注意程序的性能了。如果一定要用的话,可以采用使用deque。

const int max_size = 1000000;
const int append_size = 10000;

template<typename T>
void test_func1(T &test_list)
{
    for (int i = 0; i < append_size; i++)
    {
        int size = test_list.size();
        test_list.push_back(size);
    }
    return;
}

int main()
{
    list<int> test_list;
    deque<int> test_deque;

    for (int i = 0; i < max_size; i++)
    {
        test_list.push_back(i);
        test_deque.push_back(i);
    }

    test_func1(test_list);
    // test_func1(test_deque);
    return 0;
}


------
从时间上面看,执行时间的差距非常非常大。

# 执行 test_list
$ time ./a.out   
./a.out  60.07s user 0.02s system 99% cpu 1:00.16 total

# 执行 test_deque
$ time ./a.out 
./a.out  0.10s user 0.02s system 98% cpu 0.130 total

同理,对于需要判断容器是不是为空,更加推荐用empty()方法,而不是size()方法。

3. vector<bool>有坑

vector<bool> 避免使用, 因为vector<bool>不是容器。用deque<bool>或者vector<int>来替换。

4. insert和earse之后,原有的迭代器有可能失效

对于insert和erase对于容器的操作, 必须假设都会使所有迭代器失效。尤其是erase之后,忘记了的话,非常容易coredump了。

5. 合理使用stl
  • reserve()
    vector的空间是2的倍数增长的,每次空间扩容的时候,都需要重新拷贝原来的元素。如果已经可以确定容器需要的元素的数量,那可以提前用reserve()预定容量,stl会把capacity一次分配到位。

  • 利用swap缩容,string(s).swap(s);
    从vector中删除元素缩减了该vector的大小(size),但是并没有减小它的容量(capacity)。clear()并不会导致空间收缩 ,如果需要释放空间,可以跟空的vector交换,std::vector <t>.swap(v),c++11里shrink_to_fit()也能收缩内存。

vector<T>().swap(_vectorToBeReleased);
  • 大的对象插入vector要注意开销
    首先,insert或者push_back方法插入容器的都是原来对象的拷贝;其次,每次容器空间扩容的时候,会重新拷贝原来的对象。可以尝试改为指针,或者可以去掉一些冗余的数据,或者使用emplace_back方法。
  • 如果你的map中的元素, 不需要被排序, 那么考虑使用hashmap或者tr1中的unordered_map, 这样整体插入及查询删除效率都会有很大的提升

  • vector, list, deque的容器选择问题
    vector是堆上的连续数组, list是双向链表, deque是多个连续内存块:
    a) vector的最佳选择是知道容器中元素个数, 然后调用reserve方法, push_back效率是三者最高的, 且这样不涉及到的内存的复制及重新分配问题
    b) 需要频繁的在任意位置插入和删除, 选择list, 毫无疑问
    c) 只是在容器的头部和尾部做插入和删除, 那么尽量选择deque, 否则选择vector

  • 其他的坑:
    class A
    class B : public A
    vector<A>.push_back(inst_B),
    结果只会push_back inst_B中的A实例部分, B到A的派生出来的部分会被剔除
    解决方案是使用容器vector<A>来保存B对象的指针即可, 但是注意请自行析构vector<A>

    容器中的元素如果为指针, 请自行进行删除指针, 如vector<int *>, 因为指针不能被析构掉

  • stl线程不安全

C++开发相关注意的问题

1. 栈上定义大结构体

栈空间很有限,不能在栈上定义过大的临时对象。一般而言,用户栈只有几兆(典型大小是4M,8M),所以栈上创建的对象不能太大。如果过大了,会怎么样? 掉坑详情:https://www.jianshu.com/p/af28c76f7a28

2. 全局变量的初始化顺序

effective c++里面也提到过这个问题,全局变量的初始化顺序是不能够保证的。

3. 编译器为什么不给局部变量和成员变量赋值

不要寄希望于变量会被默认初始化为0

4. 内存拷贝小心内存越界;memcpy,memset有很强的限制,仅能用于POD结构,不能作用于stl容器或者带有虚函数的类

memset等操作要注意操作的对象和它的size是否一致等

5. 注意循环的边界

常见的for,while循环内没有index++,或者循环内走了每个分支就不会index++ ; do{}while(true) ; do{}while(恒为true)等。

6. 使用延迟计算或者被动更新,避免重复计算
6. 用c标准库的安全版本(带n标识)替换非安全版本

比如用strncpy替代strcpy,用snprintf替代sprintf,用strncat代替strcat,用strncmp代替strcmp,memcpy(dst, src, n)要确保[dst,dst+n]和[src, src+n]都有有效的虚拟内存地址空间。;多线程环境下,要用系统调用或者库函数的安全版本代替非安全版本(_r版本),谨记strtok,gmtime等标准c函数都不是线程安全的。


C++性能上优化的小技巧

  1. cpu局部性原理

    for (int i = 0; i < size; i++) {
      for (int j = 0; j < 8; j++) {
        sum = arr[i][j];
      }
    }
    
    for (int i = 0; i < 8; i += 1) {
      for (int j = 0; j < size; j++) {
        sum = arr[j][i];
      }
    }
    

    按列访问会打破局部性的原理,会导致cpu cache更多的miss,导致性能下降。

  2. 伪共享

    系统中是以缓存行(cache line)为单位存储的,缓存行通常是 64 字节。如果有多个线程操作不同的成员变量,但是相同的缓存行,这个时候会发生什么?。没错,伪共享(False Sharing)问题就发生了。

  3. 乘法比除法的cpu指令周期更少
    可以把除法运算改为乘法运算

    if (a/100 <= b)
    
    if (a <= b* 100)
    
  4. 成本比较高的系统调用的结果可以缓存起来,再一个可以容忍的有效期之内使用缓存的结果。比如glog调用gettimeofday的时候,如果在同一个时间间隔内,就用缓存,而不是在调用一次。

相关文章

  • C++后端开发的踩坑整理

    C++开发的一些经验和踩坑整理 STL相关的坑 1. std::sort()函数要求严格弱序 STL文档中要求so...

  • 小程序与后端联调踩坑

    小程序与后端联调踩坑 本次练习是以springboot作为后端开发框架、微信小程序做前端交互。 后端代码截图: s...

  • C/C++学习路线图

    文章转载自「开发者圆桌」一个关于开发者入门、进阶、踩坑的微信公众号 这里整理的C/C++学习路线图包含初中高三个部...

  • org.springframework.

    记录web开发踩过的坑 记录一个后端的小细节, springboot repository创建时, 定义的方法一定...

  • Vue 项目里戳中你痛点的问题及解决办法

    最近要求使用vue进行前后端分离开发微信公众号,不断摸索踩坑之后,总结出如下几点vue项目开发中常见的问题及解决办...

  • QT 实用代码片段 (持续更新)

    由于项目需要开始转型学习C++,GUI使用QT进行开发,开发过程中踩了不少坑,但是也积累了些宝贵经验,在这儿记录一...

  • Android 日常踩坑

    1、Android NDK开发踩坑 踩坑环境 Android Studio 3.4.1,JDK 1.8 1、为什么...

  • Flutter 开发记录

    Flutter 开发踩坑记录(干货总结)

  • Go语言之使用make所碰到的坑

    背景介绍: 开发的时候,使用make的时候,往往会踩一些坑,基于这个原因,本作者做了一个整理。 坑1:slice在...

  • 安卓开发通过JNI调用本地方法

    今天开发中第一次接触到对jni方法的调用,特此记录在开发踩过的坑。 java通过JNI调用c/c++编写的本地方法...

网友评论

      本文标题:C++后端开发的踩坑整理

      本文链接:https://www.haomeiwen.com/subject/kfnubqtx.html