C语言面试常考函数和坑
liebian365 2024-12-31 12:44 119 浏览 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);
简单说来:
- 如果size的大小大于ptr原来分配的,则扩充ptr,原来部分内存内容保持不变,新增加的内存没有被初始化哦。
- 如果ptr为NULL,函数等同于malloc(size).
- 如果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是否为空
这里面容易引起的问题就是没有判断是否为空,从而导致了问题。
相关推荐
- go语言也可以做gui,go-fltk让你做出c++级别的桌面应用
-
大家都知道go语言生态并没有什么好的gui开发框架,“能用”的一个手就能数的清,好用的就更是少之又少。今天为大家推荐一个go的gui库go-fltk。它是通过cgo调用了c++的fltk库,性能非常高...
- 旧电脑的首选系统:TinyCore!体积小+精简+速度极快,你敢安装吗
-
这几天老毛桃整理了几个微型Linux发行版,准备分享给大家。要知道可供我们日常使用的Linux发行版有很多,但其中的一些发行版经常会被大家忽视。其实这些微型Linux发行版是一种非常强大的创新:在一台...
- codeblocks和VS2019下的fltk使用中文
-
在fltk中用中文有点问题。英文是这样。中文就成这个样子了。我查了查资料,说用UTF-8编码就行了。edit->Fileencoding->UTF-8然后保存文件。看下下边的编码指示确...
- FLTK(Fast Light Toolkit)一个轻量级的跨平台Python GUI库
-
FLTK(FastLightToolkit)是一个轻量级的跨平台GUI库,特别适用于开发需要快速、高效且简单界面的应用程序。本文将介绍Python中的FLTK库,包括其特性、应用场景以及如何通过代...
- 中科院开源 RISC-V 处理器“香山”流片,已成功运行 Linux
-
IT之家1月29日消息,去年6月份,中科院大学教授、中科院计算所研究员包云岗,发布了开源高性能RISC-V处理器核心——香山。近日,包云岗在社交平台晒出图片,香山芯片已流片,回片后...
- Linux 5.13内核有望合并对苹果M1处理器支持的初步代码
-
预计Linux5.13将初步支持苹果SiliconM1处理器,不过完整的支持工作可能还需要几年时间才能完全完成。虽然Linux已经可以在苹果SiliconM1上运行,但这需要通过一系列的补丁才能...
- Ubuntu系统下COM口测试教程(ubuntu port)
-
1、在待测试的板上下载minicom,下载minicom有两种方法:方法一:在Ubuntu软件中心里面搜索下载方法二:按“Ctrl+Alt+T”打开终端,打开终端后输入“sudosu”回车;在下...
- 湖北嵌入式软件工程师培训怎么选,让自己脱颖而出
-
很多年轻人毕业即失业、面试总是不如意、薪酬不满意、在家躺平。“就业难”该如何应对,参加培训是否能改变自己的职业走向,在湖北,有哪些嵌入式软件工程师培训怎么选值得推荐?粤嵌科技在嵌入式培训领域有十几年经...
- 新阁上位机开发---10年工程师的Modbus总结
-
前言我算了一下,今年是我跟Modbus相识的第10年,从最开始的简单应用到协议了解,从协议开发到协议讲解,这个陪伴了10年的协议,它一直没变,变的只是我对它的理解和认识。我一直认为Modbus协议的存...
- 创建你的第一个可运行的嵌入式Linux系统-5
-
@ZHangZMo在MicrochipBuildroot中配置QT5选择Graphic配置文件增加QT5的配置修改根文件系统支持QT5修改output/target/etc/profile配置文件...
- 如何在Linux下给zigbee CC2530实现上位机
-
0、前言网友提问如下:粉丝提问项目框架汇总下这个网友的问题,其实就是实现一个网关程序,内容分为几块:下位机,通过串口与上位机相连;下位机要能够接收上位机下发的命令,并解析这些命令;下位机能够根据这些命...
- Python实现串口助手 - 03串口功能实现
-
串口调试助手是最核心的当然是串口数据收发与显示的功能,pzh-py-com借助的是pySerial库实现串口收发功能,今天痞子衡为大家介绍pySerial是如何在pzh-py-com发挥功能的。一、...
- 为什么选择UART(串口)作为调试接口,而不是I2C、SPI等其他接口
-
UART(通用异步收发传输器)通常被选作调试接口有以下几个原因:简单性:协议简单:UART的协议非常简单,只需设置波特率、数据位、停止位和校验位就可以进行通信。相比之下,I2C和SPI需要处理更多的通...
- 同一个类,不同代码,Qt 串口类QSerialPort 与各种外设通讯处理
-
串口通讯在各种外设通讯中是常见接口,因为各种嵌入式CPU中串口标配,工业控制中如果不够还通过各种串口芯片进行扩展。比如spi接口的W25Q128FV.对于软件而言,因为驱动接口固定,软件也相对好写,因...
- 嵌入式linux为什么可以通过PC上的串口去执行命令?
-
1、uboot(负责初始化基本硬bai件,如串口,网卡,usb口等,然du后引导系统zhi运行)2、linux系统(真正的操作系统)3、你的应用程序(基于操作系统的软件应用)当你开发板上电时,u...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- go语言也可以做gui,go-fltk让你做出c++级别的桌面应用
- 旧电脑的首选系统:TinyCore!体积小+精简+速度极快,你敢安装吗
- codeblocks和VS2019下的fltk使用中文
- FLTK(Fast Light Toolkit)一个轻量级的跨平台Python GUI库
- 中科院开源 RISC-V 处理器“香山”流片,已成功运行 Linux
- Linux 5.13内核有望合并对苹果M1处理器支持的初步代码
- Ubuntu系统下COM口测试教程(ubuntu port)
- 湖北嵌入式软件工程师培训怎么选,让自己脱颖而出
- 新阁上位机开发---10年工程师的Modbus总结
- 创建你的第一个可运行的嵌入式Linux系统-5
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)