编程我只用CPP 发布的文章

一、父子进程共享内容

相同处

全局变量,.data, .text、堆栈,环境变量,用户id工作目录。

重点:文件描述符,mmap建立的映射区。

不同处

进程ID、fork返回值、各自父进程、进程运行时间、定时器,未决信号集。

子进程复制了父进程的用户空间,遵循读时共享,写时复制原则。

- 阅读剩余部分 -

reserve方法用来给vector预留空间,预留的空间只会改变capacity的大小,不会改变size大小。resize方法表示重新调整数组大小,capacity和size都会改变。

使用reserve后,不能直接使用下标来增加元素,虽然内存是已经分配了直接使用不会报错,但是直接通过下标来复制会导致其他参数得不到更新(如size),会导致意想不到的错误。如以下代码:

int i;
vector<int> v;
v.reserve(10);
cout << "cap: " << v.capacity() << ", size: " << v.size() << endl;
for (i = 0; i < 10; i++) {
    v[i] = i;
}
cout << "cap: " << v.capacity() << ", size: " << v.size() << endl;

输出:

cap: 10, size: 0
cap: 10, size: 0

通过下标给数组中的每个元素复制,实际上本身数组的长度并没有得到增长,一旦再执行push_back就会导致前面的数据被覆盖。正确的方式是使用push_back或者insert方法插入元素。

使用Github Pages部署静态网页

一、关于Github Pages

github pages是github提供的静态文件托管服务,支持部署仓库内的静态页面文件,特别适合hexojekyll以及gitbook等应用部署网站使用。Github Pages有以下几个优点:

  1. 完全免费
  2. 支持自定义域名
  3. 支持HTTPS
  4. 免备案

其实国内像gitee.comcoding.net也都是提供Pages服务的,但是经过几年的使用感受来说,这两家相对于github还略有不足。主要体现在:

  1. coding在几年前是免费的,也支持自定义域名和HTTPS,无需备案,当时甚至支持对php动态程序的托管。如果coding一直是这种情况,我肯定毫无疑问选择coding,但是coding在17年底18年初和腾讯合作之后,功能就被阉割了。几个功能不是开会员才能使用,就是服务器得收钱。特别是在推广的Web IDE用起来完全没有之前清爽,感觉很笨重。因此后面在改版之后基本就没用过了(当时是在coding部署的hexo博客,改版后导致我后面直接连hexo都没用了T_T)。
  2. gitee也是近两年才大力改善Pages的功能,以前是不支持自定义域名的,部署之后得用用户名.gitee.com/仓库名/来访问,HTTPS功能更是没有(因为太挫了所以gitee基本不用)。现在是支持了自定义域名,但是要开通会员才能自定义域名,每年99(直接放弃)。

对比来看,github还算比较良心的,这也是为什么会选择github来托管的原因。但是github pages也有功能是不足的地方:

  1. 不支持分支部署,只支持部署master分支或者master分支下的docs目录。而上面两家国内托管机构支持多分支部署。
  2. 不支持目录部署,例如希望通过book.xxxx.com/a/和book.xxxx.com/b/分别部署两个仓库,在github pages中是不支持的。只能是通过多个自定义域名来区分,如a.book.xxxx.com和b.book.xxxx.com。

二、设置github pages

2.1 部署静态页面

pages的设置在setting页面中:

image50039403e34d74f1.png

点击后,一直往下拖就可以看到pages设置:

imagec8363663057f9ca9.png

pages目前只支持对master分支或者master的docs目录添加静态页面托管,要想托管页面,必须把文件放到master主目录或者docs目录下。而对于大多数应用(如gitbook)来说,主目录一般都是源码文件,因此建议把生成的静态文件放到docs目录下。当代码库中存在docs目录时,第二个选项会自动亮起。

imageaa67e5d9092374a8.png

点击后就可以使用username.gitee.io/projectname来访问静态页面了。

2.2 添加自定义域名

默认情况下,部署完成后,访问的URL为username.gitee.io/projectname,github支持自定义域名来重定向这个页面。

配置方法:Custom domain栏目中输入自定义的域名,点击Save即可。

我这里绑定的域名是html.book.maqian.io

image54a31ad6812de112.png

配置好后,页面会提示:

Your site is ready to be published at http://html.book.maqian.io/

现在要做的就是把域名添加指向到github pages的服务地址,有两种方法:

  1. 添加一条CNAME记录,指向username.github.io。可以参考Quick start: Setting up a custom domainCustom domain redirects for GitHub Pages sites
  2. 添加A记录,指向github服务器的IP地址,IP地址要在Troubleshooting custom domains找到。

正常情况下建议使用CNAME解析来指向github,因为域名都有套CDN,访问速度优于指向IP地址。

更多相关信息可参考Using a custom domain with GitHub Pages

添加CNAME解析

CNAME解析在DNS服务商处添加,因为打算把*.book.maqian.io都作为自定义域名,因此添加了以下CNAME解析:

imagea88cf94ba8d3a9b3.png

加好后,确认本机可以解析:

C:\Users\maqian>nslookup html.book.maqian.io
服务器:  public1.alidns.com
Address:  223.5.5.5

非权威应答:
名称:    maqianplus.github.io
Addresses:  185.199.111.153
          185.199.109.153
          185.199.108.153
          185.199.110.153
Aliases:  html.book.maqian.io

然后访问网站,部署的页面就出来了:

imagedab906d8bae5d8ef.png

2.3 强制https

github pages支持添加https,会自动申请Let's encrypt的证书,勾选上Enforce HTTPS选项就可以了:

imagedbb515b8a50c0195.png

选框无法被勾选的时候,先清空Custom domain里面的内容,点击Save,然后再勾选上Enforce HTTPS,填入自定义域名。

看到以下信息的时候,说明https证书已经在申请过程中了,耐心等待一段时间:

image8bdc5e1d6af76a8b.png

申请完成之后,勾选上选框:

image1b3d2accc9aa6543.png

再次访问就是https的了:

image409221fd63d000ce.png

TCP与UDP基本区别:

  • TCP提供的是面向连接,类似于打电话,会有握手流程。UDP无连接,类似广播。
  • TCP要求系统资源较多,UDP程序结构较简单,要求的系统资源也较少。
  • TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达。UDP尽最大努力交付,即不保证可靠交付。
  • 流模式(TCP)与数据报模式(UDP),TCP面向字节流,UDP面向报文。
  • TCP保证数据正确性,UDP可能丢包。
  • TCP保证数据顺序,UDP不保证。
  • TCP连接只能是点到点的;UDP支持一对一、一对多、多对一和多对多的交互通信。

为什么UDP有时比TCP更有优势:

UDP以其简单、传输快的优势,在越来越多场景下取代了TCP,如实时游戏。

  1. 网速的提升给UDP的稳定性提供可靠网络保障,丢包率很低,如果使用应用层重传,能够确保传输的可靠性。
  2. TCP为了实现网络通信的可靠性,使用了复杂的拥塞控制算法,建立了繁琐的握手过程,由于TCP内置的系统协议栈中,极难对其进行改进。采用TCP,一旦发生丢包,TCP会将后续的包缓存起来,等前面的包重传并接收到后再继续发送,延时会越来越大,基于UDP对实时性要求较为严格的情况下,采用自定义重传机制,能够把丢包产生的延迟降到最低,尽量减少网络问题对游戏性造成影响。

netstat是控制台命令,是一个监控TCP/IP网络的非常有用的工具,它可以显示路由表、实际的网络连接以及每一个网络接口设备的状态信息。Netstat用于显示与IP、TCP、UDP和ICMP协议相关的统计数据,一般用于检验本机各端口的网络连接情况。

- 阅读剩余部分 -

一、进程和线程

进程是表示资源分配的基本单位,又是调度运行的基本单位。例如,用户运行自己的程序,系统就创建一个进程,并为它分配资源,包括各种表格、内存空间、磁盘空间、I/O设备等。然后,把该进程放人进程的就绪队列。进程调度程序选中它,为它分配CPU以及其它有关资源,该进程才真正运行。所以,进程是系统中的并发执行的单位。

引入线程的目的是简化线程间的通信,以小的开销来提高进程的并发程度。有时称轻量级进程。 进程中的一个运行实体,是一个CPU调度单位, 资源的拥有者还是进程或称任务。事实上,引入线程主要是为了提高系统的执行效率,减少处理机的空转时间和调度切换(保护现场信息)的时间,以及便于系统管理。

- 阅读剩余部分 -

C++多态和虚函数的实现原理

一、关于多态

多态是C++中的重要内容,多态的意思是可以使用父类指针指向子类,并通过这个指针调用子类的函数。

多态可以分为两种形式:

  1. 编译时多态,主要指泛型编程。
  2. 运行时多态,主要指虚函数和子类继承。

一般我们讨论的都是运行时多态,运行时多态的几个基本条件为:

  • 继承和虚函数。
  • 父类对象指向子类对象。

多态形成的原理就是vptr指针和vtable虚函数表,当类中有虚函数时,编译器会自动生成虚函数表vtable,这个表属于这个类,所有类实例共享。同时还成一个vptr指针指向这个虚函数表,执行调用的时候,先通过vptr指针找到自己的虚函数表,然后执行表中的函数。

二、证明vptr指针存在

多态形成的必要条件是虚函数和继承,当两者其中之一都不存在的时候,编译器不会形成多态,因此也不会生成vptr指针。只有两者同时存在的时候,编译器才会生成vptr指针。

以下代码通过类的大小来证明了vptr指针的存在:

#include <iostream>
using namespace std;

class A {
public:
    A() {};
    ~A() {};
    void print() { cout << "AAAAAAAAAA" << endl; };
};

class B : public A {
public:
    B() {};
    ~B() {};
    void print() { cout << "BBBBBBBBBB" << endl; };
};

int main() {
    cout << "sizeof(A): " << sizeof(A) << endl;
    cout << "sizeof(B): " << sizeof(B) << endl;
    return 0;
}

C++对空类会分配一个字节的内存,此时的输出A和B的大小都是1:

sizeof(A): 1
sizeof(B): 1

将A中的print()函数设置为虚函数后:

virtual void print() { cout << "AAAAAAAAAA" << endl; };

两个结构体的大小会变成8,因为添加虚函数后,类中会生成vptr指针,64位系统指针是四个字节,所以A和B的大小就变成了8。

三、vptr指针的工作原理

3.1 vptr和vtable

在创建一个含有虚函数的类时,系统会为每个类生成虚函数表,存放了当前对象所有的函数列表,而vptr指针就指向这个表的地址。子类继承父类后,也会生成一个属于自己的虚函数表和vptr指针,如果子类有重写父类的虚函数,虚函数表中的函数地址就是自己类中的成员函数。执行子类调用时,先通过vptr指针定位到对应的虚函数表,然后执行对应的函数。

以上是一个图形示例,类B继承了类A,类B重写了f1f2函数,它的函数表中f1f2都指向自己的成员函数。而f3并未继承,所以指向父类。因此执行子类函数时,f1f2都是用的自己的成员函数。

关于静态联编和动态联编

说到多态肯定免不了要提到动态联编,动态联编就是指程序在运行时才能决定的运行片段,例如ifswitch代码段,它们只有在运行时才能知道下一步是什么,走哪个代码代码段。多态也是如此,运行到了虚函数的时候才能决定执行哪个函数。而静态联编就是指程序在编译阶段就能决定的事情,就像main函数,编译后就固定从它开始执行。

3.2 vptr指针绑定的顺序

子类中vptr指针的值的绑定顺序:

  1. 运行父类构造函数时,先指向父类的虚函数表。
  2. 运行子类构造函数时,再把指针指向子类的虚函数表。

通过以下代码和GDB调试器可以验证这个观点:

#include<iostream>

using namespace std;

class A {
public:
    A() {
    };
    ~A() {};
    virtual void print() { cout << "AAAAAAAAAA" << endl; };
};

class B : public A {
public:
    B() {
    };
    ~B() {};
    void print() { cout << "BBBBBBBBBB" << endl; };
};

int main() {
    A a;
    B b;

    return 0;
}

带调试模式编译:

g++ main.cpp -g -o app -std=c++11

使用GDB调试执行,先在父类构造函数中添加断点:

子类构造函数中也添加断点:

输入run执行,执行到第一个断点,是正在构造a对象的时候,此时打印出A的vptr指针地址:

继续往下执行,下一个断点依旧是在类A的构造函数处,此时正在构造对象b,因为B继承了A,所以先执行A的构造函数。此时打印出b的vptr指针地址,可以看到b对象的vptr指针和a对象中的vptr指针地址一样:

再继续执行,走到类B的构造函数,此时对象b的vptr指针就被修改成了类B自己的虚函数表地址了:

过程图示:

第一步,执行父类构造函数时,指向父类的虚函数表。

第二步,执行子类自己的构造函数时,再指向子类的虚函数表。

大四上学期的时候因为毕业设计接触到了IC卡破解,当时是花了一个星期的时间破解了学校了水卡、洗衣卡等,毕业后没有再玩过了。

前几天搬家没有门禁卡,每次都是在楼下苦等没有办法。不得已之下又又拿出工具玩了玩。这里记录下流程,以备后用!

一、准备

  1. 了解IC卡的工作原理,目前仅支持M1卡破解,一般学校一卡通和水卡都是这种类型,可以尝试破解。地铁和公交卡一半则是CPU卡,无法破解。
  2. 一个读卡模块,PN532模块+uart转接头+卡片。
  3. 如果你是为了复制卡片,待复制的卡片最好为全扇区可擦除重写。

- 阅读剩余部分 -