欢迎关注我的个人博客:www.wuyudong.com, 更多精彩文章与您分享

指针的声明与初始化

1、不恰当的指针声明

考虑如下的声明:

int* ptr1, ptr2; // ptr1为指针,ptr2为整数

正确的写法如下:

int* ptr1, *ptr2;

用类型定义代替宏定义是一个好的习惯,类型定义允许编译器检查作用域规则,而宏定义不一定会。

使用宏定义辅助声明变量,如下所示:

#define PINT int*
PINT ptr1, ptr2;

不过结果和前面所说的一致,更好的方法是使用下面的类型定义:

typedef int* PINT;
PINT ptr1, ptr2;

2、使用指针未初始化

在使用指针之前未初始化会导致运行时错误,如下面的代码:

int* p;
...
printf("%d\n", *p);

指针p未被初始化,可能含有垃圾数据

3、处理未初始化指针

  • 总是用NULL来初始化指针
  • 用assert函数
  • 用第三方工具

把指针初始化为NULL更容易检查是否使用正确,即便这样,检查空值也比较麻烦,如下所示:

int *pi = NULL;
...
if(pi == NULL) {
//不应该解引pi
} else {
//可以使用pi
}

我们可以使用assert函数来测试指针是否为空值:

assert(pi != NULL);

指针的使用问题

缓冲区溢出

缓冲区溢出是指当计算机向缓冲区内填充数据位数时超过了缓冲区本身的容量,使得溢出的数据覆盖在合法数据上,理想的情况是程序检查数据长度并不允许输入超过缓冲区长度的字符,但是绝大多数程序都会假设数据长度总是与所分配的储存空间相匹配,这就为缓冲区溢出埋下隐患。操作系统所使用的缓冲区又被称为"堆栈".。在各个操作进程之间,指令会被临时储存在"堆栈"当中,"堆栈"也会出现缓冲区溢出。

下面几种情况可能导致缓冲区的溢出:

  • 访问数组元素时没有检查索引值

  • 对数组指针做指针算术运算时不够小心

  • 用gets这样的函数从标准输入读取字符串

  • 误用strcpy和strcat这样的函数

1、测试NULL

使用malloc这样的函数的时候一定要检查返回值,否则可能会导致程序的非正常终止,下面是一般的方法:

float *vector = malloc( * sizeof(float));
if(vector == NULL) {
//malloc分配内存失败
} else {
//处理vector
}

2、错误使用解引操作

声明和初始化指针的常用方法如下:

int num;
int *pi = #

下面是一种看似等价但是错误的声明方法:

int num;
int *pi;
*pi = #

3、迷途指针

参见《C迷途指针

4、越过数组边界访问内存

没有什么可以阻止程序访问为数组分配的空间以外的内存,下面的例子中,我们声明并初始化了三个数组来说明这种行为:

#include<stdio.h>
int main()
{
char firstName[] = "";
char middleName[] = "";
char lastName[] = "";
middleName[-] = 'X';
middleName[] = 'X';
middleName[] = 'X';
printf("%p %s\n", firstName, firstName);
printf("%p %s\n", middleName, middleName);
printf("%p %s\n", lastName, lastName);
return ;
}

运行结果如下:

下图说明了内存分配情况:

5、错误计算数组长度

将数组传给函数时,一定要同时传递数组长度,这个信息帮助函数避免越过数组边界

#include<stdio.h>
void replace(char buffer[], char replacement, size_t size)
{
size_t count = ;
while(*buffer && count++ < size) {
*buffer = replacement;
buffer++;
}
}
int main()
{
char name[];
strcpy(name, "Alexander");
replace(name, '+', sizeof(name));
printf("%s\n", name);
return ;
}

6、错误使用sizeof操作符

其中一个例子是试图检查指针边界但方法错误

#include<stdio.h>
int main()
{
int buffer[];
int *pbuffer = buffer;
for(int i = ; i < sizeof(buffer); i++) {
*(pbuffer++) = ;
}
return ;
}

改为:i < sizeof(buffer) / sizeof(int);

7、有界指针

有界指针是指指针的使用被限制在有效的区域内,C没有对这类指针提供直接的支持,但是可以自己显示地确保。如下所示:

#define SIZE 32
char name[SIZE];
char *p = name;
if(name != NULL) {
if(p >= name && p < name + SIZE) {
//有效指针,继续
} else {
//无效指针,错误分支
}
}

一种有趣的变化是创建一个指针检验函数;

下面的代码定义一个函数消除无效指针:

int valid(void *ptr) {
return (ptr != NULL);
}

下面的代码依赖于_etext的地址,定义于很多的类linux操作系统,在windows上无效:

#include <stdio.h>
#include <stdlib.h> int valid(void *p) {
extern char _etext;
return (p != NULL) && ((char*) p > &_etext);
} int global; int main(void) {
int local; printf("pointer to local var valid? %d\n", valid(&local));
printf("pointer to static var valid? %d\n", valid(&global));
printf("pointer to function valid? %d\n", valid((void *)main)); int *p = (int *) malloc(sizeof(int));
printf("pointer to heap valid? %d\n", valid(p));
printf("pointer to end of allocated heap valid? %d\n", valid(++p));
free(--p);
printf("pointer to freed heap valid? %d\n", valid(p));
printf("null pointer valid? %d\n", valid(NULL)); return ;
}

在linux平台运行结果如下:

pointer to local var valid? 1
pointer to static var valid? 1
pointer to function valid? 0
pointer to heap valid? 1
pointer to end of allocated heap valid? 1
pointer to freed heap valid? 1
null pointer valid? 0
另一种方法是利用ANSI-C和C++的边界检查工具(CBMC)
 

C安全问题与指针误用的更多相关文章

  1. 《深入理解C指针》

    <深入理解C指针> 基本信息 原书名:Understanding and using C pointers 作者: (美)Richard Reese 译者: 陈晓亮 丛书名: 图灵程序设计 ...

  2. 深入理解C指针----学习笔记

      深入理解C指针     第1章 认识指针   理解指针的关键在于理解C程序如何管理内存,指针包含的就是内存地址.     1.1 指针和内存   C程序在编译后,以三种方式使用内存: 1. 静态. ...

  3. 部分博文目录索引(C语言+算法)

    今天将本博客的部分文章建立一个索引,方便大家进行阅读,当然每一类别中的文章都会持续的添加和更新(PS:博文主要使用C语言) 博客地址:http://www.cnblogs.com/archimedes ...

  4. C++类型引用浅析

    C++类型引用浅析 引言 从最早被Bjarne Stroustrup 发明,作为C语言的扩展,到广为人知C++98标准,再到最新的C++11.C++14和C++17标准,C++一直在不断地进步.演化. ...

  5. c/c++和java达到swap不同功能

    首先我们来看看c/c++实施swap性能 void swap ( int & a, int & b) { int Temp; temp = a; a = b; b = temp; } ...

  6. 2 Java对象的创建过程

    JAVA中创建对象直接new创建一个对象,对么对象的创建过程是怎样的呢? 程序运行过程中有许多的对象被创建出来.那么对象是如何创建的呢? 一 对象创建的步骤 1 遇到new指令时,检查这个指令的参数是 ...

  7. 面经 cisco

    1. 优先级反转问题及解决方法 (1)什么是优先级反转 简单从字面上来说,就是低优先级的任务先于高优先级的任务执行了,优先级搞反了.那在什么情况下会生这种情况呢? 假设三个任务准备执行,A,B,C,优 ...

  8. javascript 误用this指针 的情况

    理解了this指针后,我们再来看看一些很容易误用this指针的情况. 示例1——内联式绑定Dom元素的事件处理函数 <script type="text/javascript" ...

  9. C++智能指针简单剖析

    导读 最近在补看<C++ Primer Plus>第六版,这的确是本好书,其中关于智能指针的章节解析的非常清晰,一解我以前的多处困惑.C++面试过程中,很多面试官都喜欢问智能指针相关的问题 ...

随机推荐

  1. 直接把数据库中的数据保存在CSV文件中

    今天突然去聊就来写一个小小的demo喽,嘿嘿 public partial class Form1 : Form { public Form1() { InitializeComponent(); } ...

  2. WPF中模板选择和DataContext的一些使用

    如图样: View结构 MainView(MainViewModel)|---Guide1View(Guide1ViewModel)|---Guide2View(Guide2ViewModel) |- ...

  3. 交叉编译mips平台上valgrind

    STEP 1:下载最新版本的valgrind:http://www.valgrind.org/downloads/valgrind-3.9.0.tar.bz2 目前支持的平台,在官网上列表如下:{x8 ...

  4. 数据库收缩:NOTRUNCATE与TRUNCATEONLY

    在进行数据库收缩时,我们有2个可用选项:NOTRUNCATE,TRUNCATEONLY.这篇文章我们会详细讨论下这2个选项的具体区别. NOTRUNCATE 当你对数据库收缩命令提供NOTRUNCAT ...

  5. Java魔法堂:打包知识点之jar

    一.前言    通过eclipse导出jar包十分方便快捷,但作为码农岂能满足GUI的便捷呢?所以一起来CLI吧! 二.JAR包 JAR包是基于ZIP文件格式,用于将多个.java文件和各种资源文件, ...

  6. 五、Request

    1.      Request 由于python函数所有变量都没有显示类型声明,特别是函数的输入参数,输出参数,因此在阅读代码时会造成比较大的困扰,比如大部分处理函数都有request输入参数,不同模 ...

  7. ACM中的浮点数精度处理

    在ACM中,精度问题非常常见.其中计算几何头疼的地方一般在于代码量大和精度问题,代码量问题只要平时注意积累模板一般就不成问题了.精度问题则不好说,有时候一个精度问题就可能成为一道题的瓶颈,让你debu ...

  8. 【原创】Silverlight客户端发起WebRequest请求分析

    Silverlight网站部署后,客户端浏览器访问的时候会 下载 网站的xap文件包等信息,把程序代码放到本地执行,因为本地机器上安装了silverlight运行库. 所以如果silverlight前 ...

  9. sql语句分页多种方式ROW_NUMBER()OVER

    sql语句分页多种方式ROW_NUMBER()OVER 摘自: http://www.cnblogs.com/CodingArt/articles/1692468.html 方式一 select to ...

  10. csharp:引入app.manifest,程序在win7下以管理员权限运行配置方法

    https://msdn.microsoft.com/en-us/library/windows/desktop/hh848036(v=vs.85).aspx https://msdn.microso ...