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

时间:2008-11-12 06:56:38

标签: c

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

26 个答案:

答案 0 :(得分:24)

if (c = 1) // insert code here

答案 1 :(得分:20)

if(a == true);
{
  //Do sth when it is true. But it is allways executed.
}

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

for(i=0; i<max_iterations;i++);
{
  //Do sth but unexpectedly only once
}

答案 2 :(得分:12)

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

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

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

就像man strcpy说的那样:

  

如果是目标字符串   strcpy()不够大(那个   是的,如果程序员是愚蠢的   或懒惰,未能检查大小   在复制之前)然后任何可能   发生。溢出固定长度   字符串是最喜欢的饼干   技术

我很尴尬。现在是提交seppuku:)的好时机。

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

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

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

答案 3 :(得分:12)

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

  • 忘记字符串末尾的\0
  • 为包含n个字符的字符串分配n个字符。
  • 忘记了switch语句中的中断。
  • '广告素材'宏观使用。

答案 4 :(得分:6)

for(int i = 0; i<10; ++i)
  //code here
  //code added later

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

答案 5 :(得分:5)

未初始化的数据。

答案 6 :(得分:4)

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

  • 我不编写分页算法 - 操作系统极客会为我做这些。
  • 我不编写数据库缓存方案 - 数据库极客为我做这件事。
  • 我没有构建L2处理器缓存 - 硬件爱好者为我这样做。

而我管理记忆。

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

答案 7 :(得分:4)

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

改为使用exec *()。

当然,这不是C独有的。

答案 8 :(得分:3)

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

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

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

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

答案 9 :(得分:3)

我把危险的定义称为“我们可能会附带该错误并且仅在几年之后发现它会延迟”:

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中,你可以很容易地拍摄自己的脚,一个关于镜头脚的话题可能会在数千页内出现。

答案 10 :(得分:3)

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

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

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

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

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

答案 11 :(得分:2)

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

指针是“未初始化”

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

如果运气好,取消引用操作会立即崩溃或停止(Java就是这样做的 方式)。

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

答案 12 :(得分:2)

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

答案 13 :(得分:2)

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

if(55000 < my_var < 65000)

我的ida是这样检查的:

if( (55000<my_var) < 65000)

但这是等效的或结果

if( (55000<my_var) || (my_var<65000))

结果是if测试始终为真。

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

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

 main()
 {
   BYTE *data
   get_data(&data);
   free(data);
 }

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

答案 14 :(得分:1)

不间断地切换案例。

答案 15 :(得分:1)

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

答案 16 :(得分:1)

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

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

我将其带入C编程:

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

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

答案 17 :(得分:1)

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

耸肩

生活和学习。

答案 18 :(得分:1)

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

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

the explosion and total loss an Ariane V rocket

答案 19 :(得分:1)

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

答案 20 :(得分:1)

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

答案 21 :(得分:0)

#include <string>

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

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

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

答案 22 :(得分:0)

if (importantvar = importantfunction() == VALID_CODE)

这就是我的意思:

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

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

答案 23 :(得分:0)

我记得有两个错误:

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

答案 24 :(得分:0)

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

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

答案 25 :(得分:0)

忘了将;放在最后。  超出}。  错误地键入了,

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