0%

这里讨论C语言标准库中各类常用函数,以及它们的高危情况。

1、atoi 函数

这个函数是转换输入字符串转换为整型数。

对于该函数的实现需要考虑以下几个方面:

  1. 输入字符串为NULL;
  2. 输入的字符包含前导的空格;
  3. 输入开始是否包含符号‘+’、‘-’;
  4. 输入的字符是否合法(对于十进制‘0’~‘9’为合法的输入);
  5. 计算出的数值为 long int,足够判断溢出;
  6. 数据溢出的处理(上溢出时,返回最大正数;下溢出时,返回最大负数);
阅读全文 »

先写出两者的3点区别,后面再具体分析:

  1. 当引用被创建时,它必须被初始化,而指针则可以在任何时候被初始化
  2. 一旦一个引用被初始化为指向一个对象,它就不能被改变为对另一个对象的引用,而指针可以在任何时候指向另一个对象
  3. 不可能有NULL引用,必须确保引用是和一块合法的存储单元关联,而指针可以初始化为NULL

从概念上讲,指针(pointers)从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变,这点后面再详细叙述。

而引用(references)是一个别名,它在逻辑上不是独立的,它的存在具有依附性,所以引用必须在一开始就被初始化,而且其引用的对象在其整个生命周期中是不能被改变的,自始至终只能依附于同一个变量。

阅读全文 »

一、运算符的定义

运算符重载就是运算符的“一符多用”。重载运算符是具有特殊名称的函数:保留字 operator 后接需定义的操作符符号。像任意其他函数一样,重载操作符具有返回类型和形参表,每个操作符用于内置类型都有关联的定义,当内置操作符和类型上的操作存在逻辑对应关系时,操作符重载最有用,最直观,使用重载操作符并不是创造命名操作。

二、在哪种情况下使用哪种重载运算符的方式合适?

C++ 提供了两种重载运算符的方式,在大多数情况下:

阅读全文 »

任何一个对C稍稍有了解的人都知道malloc、calloc、free。前面两个是用户态在堆上分配一段连续(虚拟地址)的内存空间,然后可以通过free释放,但是,同时也会有很多人对其背后的实现机制不了解。
这篇文章则是通过介绍这三个函数,并简单的予以实现,对比现有C的标准库实现(glibc等)相比,并不是特别高效,我们重在阐述背后的基本原理。

一、C程序的存储空间布局

图1
这里写图片描述

  • text:整个用户空间的最低地址部分,存放的是指令(程序所编译成的可执行机器码)。可共享,即使是频繁操作执行的程序,在存储器中也只需有一个副本,通常是只读的。
  • initialized data(data):存放初始化过的全局变量,包含了程序中需明确地赋初值的变量。
  • uninitialized data(bss):存放的是未初始化过的全局变量,在程序开始执行之前,内核将此段中的数据初始化为0或者NULL。
  • heap:堆,自低地址向高地址增长,后面重点剖析
  • stack:栈,自高地址向低地址增长,自动变量以及每次函数调用时所需保存的信息都存放在此段中。

二、Heap 内存模型

阅读全文 »

这里我们用类String 来介绍这两个函数:

拷贝构造函数是一种特殊构造函数,具有单个形参,该形参(常用const修饰)是对该类类型的引用。当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式使用拷贝构造函数。为啥形参必须是对该类型的引用呢?试想一下,假如形参是该类的一个实例,由于是传值参数,我们把形参复制到实参会调用拷贝构造函数,如果允许拷贝构造函数传值,就会在拷贝构造函数内调用拷贝构造函数,从而形成无休止的递归调用导致栈溢出。

1
2
string(const string &s);
//类成员,无返回值

赋值函数,也是赋值操作符重载,因为赋值必须作为类成员,那么它的第一个操作数隐式绑定到 this 指针,也就是 this 绑定到指向左操作数的指针。因此,赋值操作符接受单个形参,且该形参是同一类类型的对象。右操作数一般作为const 引用传递。

1
2
string&	operator=(const string &s);
//类成员,返回对同一类类型(左操作数)的引用

拷贝构造函数和赋值函数并非每个对象都会使用,另外如果不主动编写的话,编译器将以“位拷贝”的方式自动生成缺省的函数。在类的设计当中,“位拷贝”是应当防止的。倘若类中含有指针变量,那么这两个缺省的函数就会发生错误。这就涉及到深复制和浅复制的问题了。

拷贝有两种:深拷贝,浅拷贝

阅读全文 »