你在C中犯的最危险的编程错误是什么?

我是一名中级C程序员。 如果您之后发现任何编码错误,那么它对整个应用程序来说是最危险/最有害的,请分享该代码或说明。 我想知道这一点,因为将来我可能会遇到这种情况,我希望得到你的建议,以避免这样的错误。

几年前,我接到了我前同事的电话,告诉我他必须用我的代码解决的问题,这是一个信用卡交易的路由器。

卡号前缀由6位BIN(银行识别码)和银行自行决定使用的额外几位数字组成,例如银行为Visa Classic卡456789提供BIN,并保留2个额外数字以表示子产品,如01为学生用卡,02与当地百货商店的联名卡等。 在这种情况下,卡前缀(基本上是产品标识符)变为8位数。 当我对这部分进行编码时,我认为9位数“对每个人来说应该足够了”。 我运行2年,直到有一天银行制作一个10位长前缀的新卡产品(不知道他们为什么需要它)。 不难想象发生了什么 – 路由器发生了分离,整个系统停止了,因为它没有交易路由器就无法运行,该银行的所有ATM(该国最大的ATM)在几小时内变得无法运行,直到找到问题并且固定。

我不能在这里发布代码,因为我没有它,其次它是公司的版权所有,但不难想象strcpy()没有检查目标缓冲区的大小。

就像man strcpy所说:

如果strcpy()的目标字符串不够大(即,如果程序员是愚蠢或懒惰的,并且在复制之前未能检查大小),则可能发生任何事情。 溢出的固定长度字符串是最喜欢的cookies技术。

我很尴尬。 这是一个犯seppuku的好时机:)

但我很好地吸取了教训,并且不要忘记(通常:))来检查目标缓冲区的大小。 我不建议你以艰难的方式学习它 – 只需养成在strcpy()strcat()之前检查目标缓冲区的习惯。

编辑:来自Healthcarel的好建议 – 使用strncpy()而不是strcpy() 。 它不会添加尾随0但我通常使用以下宏来解决它:

#define STRNCPY(A,B,C) do {strncpy(A,B,C); A[C] = 0; } while (0)

 if (c = 1) // insert code here 
 if(a == true); { //Do sth when it is true. But it is allways executed. } 

编辑 :同一错误的另一种变体。

 for(i=0; i 

这已经很久了,但有些事情你永远不会忘记;-)。

  • 忘记字符串末尾的\0
  • 为包含n个字符的字符串分配n个字符。
  • 忘记switch语句中的break。
  • ‘创意’宏观使用。
 for(int i = 0; i<10; ++i) //code here //code added later 

请注意,后面添加的代码不在for循环中。

未初始化的数据。

我在C中做过的最危险的事情是尝试编写管理我自己记忆的代码。 实际上,这意味着我在C中做过最危险的事情就是编写C代码 。 (我听说这些天你可以绕过它。嘻嘻哈哈的理智。在适当的时候使用这些方法!)

  • 我不写分页算法 – 操作系统极客为我这样做。
  • 我不写数据库缓存方案 – 数据库极客为我这样做。
  • 我不构建L2处理器缓存 – 硬件极客为我做这件事。

而且我没有管理记忆。

其他人为我管理我的记忆 – 一个能够比我更好地设计,并且能够比我更好地测试,并且能够比我更好地编码的人,并在他们做出关键的安全性妥协错误时进行修补,这些错误只会在10年后被注意到,因为绝对每个试图分配内存的人都会在某些时候失败。

system()在参数中包含一些用户提供的字符串。 popen()同样如此。

请改用exec *()。

当然,这不是C独有的。

你应该更多地担心一些小错误。 大型/壮观的错误通常记录在书中(原因是它们是坏的,替代方法等)。

这是一个很小的设计/编码错误,因为它们往往会加起来。

所以我的建议是尝试阅读Kernighan编写或共同撰写的书籍(“C编程语言”,“编程实践”等),因为它们充满了常识(对于有经验的C程序员来说很常见)的建议和列表在避免小错误和大错误方面非常有用的原则。

他们还列出了许多潜在的重大错误,因此他们回答了您的初步问题。

我把危险的定义称为“我们可能会携带那个bug并且仅在几年之后才会发现它”:

 char* c = malloc(...); . . . free(c); . . . c[...] = ...; 

要么

 // char* s is an input string char* c = malloc(strlen(s)); strcpy(c, s); 

但是如果你编写多平台(不限于x86 / x64),这也很棒:

 char* c = ...; int i = *((int*)c); // <-- alignment fault 

如果你的缓冲区来自不受信任的来源......基本上大多数代码都是危险的。

但是,无论如何,在C中你很容易用脚射击自己,一个关于射击脚的话题可以绕过数千页。

我在这里与Pat Mac达成协议(尽管他的贬低)。 你可以在C中做的最危险的事情就是将它用于重要的事情。

例如,一个合理的语言将默认检查数组边界并立即停止您的程序(引发exception或某事),如果你试图在它之外徘徊。 阿达做到了这一点。 Java做到了这一点。 大量其他语言都这样做。 不是C.在这种语言的缺陷中建立了整个黑客行业。

一个人的经历。 我曾与一家运行飞行模拟器网络的公司合作,并与reflection(共享)内存硬件捆绑在一起。 他们遇到了一个他们无法追踪的令人讨厌的崩溃错误,所以我们两个最好的工程师被派到那里追踪它。 他们用了2个月。

事实certificate,其中一台机器上的C循环中存在一个错误的错误。 当然,一种称职的语言会阻止事情发生,但是C让它继续在数组末尾的下一个位置写入一段数据。 该内存位置碰巧被网络上的另一台机器使用,该机器将其传递给第三台机器,该机器使用(垃圾)值作为数组索引。 由于这个系统也是用C语言编写的,因此它并不关心它是否在其数组之外的索引方式,以及在其程序中丢弃半随机存储器位置。

因此,由于缺少数组边界检查,一个简单的易于制造的错误导致计算机中的随机崩溃导致两个完整的跳跃远离错误的来源! 公司成本:他们最好的工程师时间是4个人月,加上其他工程师和支持人员花费了很多,加上所有模拟器的停机时间都不正常。

首次分配指针时,它没有指针。

指针是“未初始化”

对坏指针的取消引用操作是严重的运行时错误。

如果幸运的话,取消引用操作将立即崩溃或停止(Java以这种方式运行)。

如果你运气不好,坏的指针取消引用会破坏一个随机的内存区域,稍微改变程序的运行,以便以后无限期地出错。 必须先为每个指针指定一个指针,然后才能支持取消引用操作。

想到两件事。 首先是嵌入式C(MCU)中的一个function,我尝试对定时器值作为输入函数进行一些限制。 所以我写了

 if(55000 < my_var < 65000) 

我的ida是这样检查的:

 if( (55000 

但这是等效的或结果

 if( (55000 

结果结果是if测试总是正确的。

这是一个指针错误。 (这里简单介绍)

 get_data(BYTE **dataptr) { ubyte* data = malloc(10); ... code ... *dataptr = &data[1]; } main() { BYTE *data get_data(&data); free(data); } 

因此,每次调用get_data()函数时都会导致丢失1个字节的内存

 while(a) { // code - where 'a' never reaches 0 :( } 

使用非限制字符串函数(如strcpy()或strcmp()),而不是像strncpy()和strncmp()这样的安全版本。

将虚拟地址传递给DMA引擎是最糟糕的,不完全与C相关,但我假设99%的DMA相关内容用C语言编写,所以它是匹配的。 这个小错误导致内存损坏,花了我1.5个月才发现。

开关盒没有rest。

作为一名Lisp程序员,我习惯于缩进括号,如:

 (cond ((eq a foo)(bar ... .... )) ) 

我将其带入C编程:

 if (a == foo){ bar(...); .... } 

然后我在C中开始了一个大型项目,另一个程序员不得不在我的代码附近进行更改。 他误读了我的右括号,过早地释放了一些记忆。 这导致了一个非常微妙的错误,发生在关键时刻。 当它被发现时,他受到了严厉指责。 但你可以说这是我的错。 至少可以说,这并不好玩。

忘记架构约束并愉快地memcpy()进入微控制器上的内存映射I / O区域。 神奇的烟雾从试验台上释放出来。

我正在处理动态分配的2D数组,而不是free()’n行,我决定释放M列。 这对于N == M的较小输入来说很好,但是在大输入时,我只能释放()50%的分配。

活到老,学到老。

这是一个着名的历史例子(不是我做过的),但是

 double d; // d gets populated with a large number from somewhere short s = d ; // overflow 

导致 阿丽亚娜V火箭 的爆炸和全部损失 。

要照顾的一件事是数组边界。 如果你走出界限,运气不好你最终可能会覆盖用于其他数据的内存。

与此相关的一个令人讨厌的错误是在函数中超出静态数组变量的范围。 最终作为一个函数改变了调用函数的局部变量的值。 这不是那么简单的调试..

我记得两个错误:

  1. 从创建它的函数中返回自动变量的地址;
  2. 将字符串复制到未初始化和未分配的指向char的指针。
 #include  

我认为C本身支持字符串(使用Metroworks codewarrior,大约8年前)。

我做了这个,最终项目大约有15,000行代码。 我使用这个库来完成与字符串有关的所有事情(追加,拆分等)只是为了让TA不能编译我的任务(使用GCC)。

我几乎没有了解到metroworks已经创建了自己的字符串库。 我失败了那堂课。

 if (importantvar = importantfunction() == VALID_CODE) 

这是我的意思:

 if ((important var = importantfunction()) == VALID_CODE) 

当我认为它像后者一样工作时,这导致了许多小时的调试麻烦。

忘了放; 在末尾。 过剩} 。 错误输入a ,

这些让我疯了几个小时,发现我的代码出了什么问题。