百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

C语言面试常考函数和坑

liebian365 2024-12-31 12:44 111 浏览 0 评论

一 前言

思维还是比较奇怪的东西,面临未知的时候充满了谨慎,对于自己稍微熟悉的东西,又会犯自大的问题,从而有些理所当然了,所以任何时候谨慎是个好习惯,用任何函数的时候多读读API的文档说明,可以避免不少坑的。根据我的经验来说,任何自己忽略的事情,总会让自己付出代价,或早或晚,还是那句话,出来混,底子要扎实了,不然早晚会还的。

二 容易出差的函数

2.1 snprintf返回值

对C程序来说缓冲区溢出攻击发生的代码,多是使用没有带长度的api,比如使用strcpy,sprintf,作为替代,常常可以使用strncpy和snprintf 替代,但是这两个函数也是有坑的。

   #include <stdio.h>
    int snprintf(char *str, size_t size, const char *format, ...);

测试方法:

void test_snprintf(void)
{
   char abc[10] ={0};
   int len = snprintf(abc,sizeof(abc),"%s","def");
   printf("copy len:%d str:%s\n",len,abc);
  
   char * need_copy = "012345678910";
   int len2 = snprintf(abc,sizeof(abc),"%s",need_copy);
   printf("copy len2:%d str:%s\n",len2,abc);
}

snprintf 是按照格式要求,最多复制size个字符到str中(实际是size-1个,最后是结束符0)。 有三种返回值: 1、 负数: 表示出错; 2、 如果str足够大,那么返回的是实际打印的数值,比如我们的例子len为3. 3、 如果str不够大,比如第二种情况,abc的大小为10,而我们要复制的是12个字符, 那么复制了9个字符到str中,且在尾部补0,但是注意了,返回的是原始字符的长度,即12,这个地方很奇怪吧。

copy len:3 str:def
copy len2:12 str:012345678

常常使用snprintf 做字符串的连接,挺好用的,只是要注意下返回值不是打印多少就返回多少的。

2.2 strncpy

函数原型:

       #include <string.h>
       char *strcpy(char *dest, const char *src);
       char *strncpy(char *dest, const char *src, size_t n);

strncpy说明:

     The strncpy() function is similar, except that at most n bytes of src are copied.  Warning: If there is no null byte among the first  n  bytes  of  src,  the string placed in dest will not be null-terminated.

strncpy 一般用来替代strcpy的,比strcpy安全一点,即最多从src中copy n个字符到dst中,如果这n个字符没有包含null,则函数不会补全。 可能的实现是这样的:

  char * strncpy(char *dest, const char *src, size_t n)
 {
         size_t i;
         for (i = 0; i < n && src[i] != '\0'; i++)
                   dest[i] = src[i];
         for ( ; i < n; i++)
                   dest[i] = '\0';
               return dest;
 }

从上面的可能实现看出,n如果远大于src的长度,会对其余的部分补0。 举个例子:

#include <stdio.h>
#include <string.h>
void teststrncpy(void)
{
   char dst[8];
   char * src = "123456789";
   char * ndst = strncpy(dst,src,sizeof(dst));
   printf("%s\n",ndst);
}
int main(void)
{
  teststrncpy();
  return 0;
}

以上的bug可以看出来嘛,拷贝了8个字符到dst中,因为src长度是大于8的,所以拷贝过去的没有null结束符,如果dst没有初始化为0的话,会导致字符串使用异常了,我实际测试中倒是发现会在dst的下一个地址设置为null,几次测试都是,不知道是不是巧合。

我们来改下,改成如下:

void teststrncpy(void)
{
   char dst[18];
   memset(dst,0x1,18);
   char * src = "123456789";
   char * ndst = strncpy(dst,src,sizeof(dst));
   printf("%s\n",ndst);
}

按照strncpy的逻辑,是将大于src长度后面的空间设置为NULL的,调试看看:

(gdb) p  *dst@10
$8 = "\001\001\001\001\001\001\001\001\001\001"
(gdb) n
9          char * ndst = strncpy(dst,src,sizeof(dst));
(gdb) p sizeof(dst)
$9 = 18
(gdb) n
10         printf("%s\n",ndst);
(gdb) p *dst@10
$10 = "123456789"
(gdb) p /x dst
$11 = {0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 
  0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}

果然把我们原来设置的,给设置为NULL了。 通常的安全用法:

           if (buflen > 0) {
               strncpy(buf, str, buflen - 1);
               buf[buflen - 1]= '\0';
           }

这样写的话,如果buf不够长,仍然可以正常工作,但是str会被截断长度为buflen-1.

2.3 fwrite返回值

函数原型:

 #include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb,
                     FILE *stream);

fwrite含义如下:

       The function fwrite() writes nmemb items of data, each size bytes long, to the stream pointed to by stream, obtaining them from the location given by ptr.

简单地说是向stream写入数据项,这些数据项单个大小为size,数量为nmemb,数据项的首地址为ptr。 例子:

void test_fwrite(void)
{
  FILE * fp = fopen("abc.bin","w+");
  if (fp == NULL)  return;
  int  arrays[5] ={1,2,3,4,5};
  int len = fwrite(&arrays,sizeof(int),5,fp);
  fclose(fp);
  printf("test fwrite:%d",len);
}

猜下输出的len是多少,结果如下:

test fwrite:5

返回值不是写入的字节数,而是写入的数据项的个数。

2.4 malloc和realloc、free

原型说明:

       #include <stdlib.h>

       void *malloc(size_t size);
       void free(void *ptr);
       void *calloc(size_t nmemb, size_t size);
       void *realloc(void *ptr, size_t size);
       void *reallocarray(void *ptr, size_t nmemb, size_t size);

这两个是内存分配和释放的函数比较熟悉吧,那么来看看下面代码:

 #include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
   char * p = malloc(0);
   if (p == NULL) printf("Malloc return null\n");
   else printf("Malloc return not null\n");
   *p = 0;
}

请问下面的代码会core嘛,答案是不会,看看malloc的说明:

     The  malloc()  function  allocates size bytes and returns a pointer to the allo‐
       cated memory.  The memory is not initialized.  If size is 0, then  malloc()  re‐
       turns  either  NULL,  or  a  unique pointer value that can later be successfully
       passed to free().

看这个解释,如果size为0,则malloc要么返回NULL,要么返回一个后面可以被成功释放的非NULL指针,这里面系统还是会给p分配一个一个字节的空间的,所以不会有问题。 不过我们的代码忘记free了,我们free两次会怎么样?

int main(void)
{
   char * p = malloc(0);
   if (p == NULL) printf("Malloc return null\n");
   else printf("Malloc return not null\n");
   *p = 0;
   free(p);
   free(p);
}

结果发生异常,如下:

miao@ubuntu-lab:~/c-test$ ./a.out
Malloc return not null
free(): double free detected in tcache 2
Aborted (core dumped)

那么如果内存free后,设置为null那,看下:

free(p); p= NULL; free(p);

测试了下正常的,来看下解释:

The free() function frees the memory space pointed to by ptr,  which  must  have
been  returned  by  a previous call to malloc(), calloc(), or realloc().  Other‐
wise, or if free(ptr) has already been called before, undefined behavior occurs.
If ptr is NULL, no operation is performed.

如果释放了一个已经释放的指针结果是未定义的,如果释放NULL,则什么都不会发生。那,如果释放多次NULL是没有问题,不会core的,也不会引起异常。所以释放完毕内存后,设置指针为NULL是个好习惯。

还要注意到释放的指针必须是malloc等函数返回的改动不行,看看下面代码:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
   char * p =(char*) malloc(4);
   if (p == NULL) printf("Malloc return null\n");
   else printf("Malloc return not null\n");
   *p =4;
   free(p+1);
}

我们把申请到p指针移动了一位释放,现在会怎么样,是core还是正常只是内存泄漏那,结果是core,如下:

miao@ubuntu-lab:~/c-test$ ./a.out
Malloc return not null
free(): invalid pointer
Aborted (core dumped)

realloc 也是个奇葩,它实现的功能不单一,看看解释:

       The realloc() function changes the size of the memory block pointed to by ptr to
       size  bytes.   The contents will be unchanged in the range from the start of the
       region up to the minimum of the old and new sizes.  If the new  size  is  larger
       than  the  old  size, the added memory will not be initialized.  If ptr is NULL,
       then the call is equivalent to malloc(size), for all values of size; if size  is
       equal  to  zero,  and ptr is not NULL, then the call is equivalent to free(ptr).
       Unless ptr is NULL, it must have been returned by an earlier call  to  malloc(),
       calloc(), or realloc().  If the area pointed to was moved, a free(ptr) is done.

       void *realloc(void *ptr, size_t size);

简单说来:

  1. 如果size的大小大于ptr原来分配的,则扩充ptr,原来部分内存内容保持不变,新增加的内存没有被初始化哦。
  2. 如果ptr为NULL,函数等同于malloc(size).
  3. 如果size为0,ptr不为null,则等同于free(ptr),当然这个ptr也必须是malloc等返回的,此时realloc返回为NULL。

看看以下代码:

void * ptr = realloc(ptr,size);
if (ptr != NULL) {
   // 业务处理
}else {
  // 错误处理
}

如果realloc失败,则返回NULL,而参数ptr并未释放,被我们清空了,导致内存无法释放了,这个隐藏的挺深的,不容易看出来。

正常处理方式:

void *ptmp = realloc(ptr,size);
if ( ptmp != NULL) {
    ptr = ptmp;
  // 业务处理
}else {
// 错误处理
}

通过重新新定义个变量临时保存就好了。

void * newp = realloc(oldp ,size);
// 此处要判断newp是否为空

这里面容易引起的问题就是没有判断是否为空,从而导致了问题。

相关推荐

4万多吨豪华游轮遇险 竟是因为这个原因……

(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...

“菜鸟黑客”必用兵器之“渗透测试篇二”

"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...

科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白

作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...

麦子陪你做作业(二):KEGG通路数据库的正确打开姿势

作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...

知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势

智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...

每日新闻播报(September 14)_每日新闻播报英文

AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...

香港新巴城巴开放实时到站数据 供科技界研发使用

中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...

5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper

本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...

Qt动画效果展示_qt显示图片

今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...

如何从0到1设计实现一门自己的脚本语言

作者:dong...

三年级语文上册 仿写句子 需要的直接下载打印吧

描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...

C++|那些一看就很简洁、优雅、经典的小代码段

目录0等概率随机洗牌:1大小写转换2字符串复制...

二年级上册语文必考句子仿写,家长打印,孩子照着练

二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...

一年级语文上 句子专项练习(可打印)

...

亲自上阵!C++ 大佬深度“剧透”:C++26 将如何在代码生成上对抗 Rust?

...

取消回复欢迎 发表评论: