C/C++中malloc的强制转换

因为数据结构课在使用malloc函数的时候一直很迷惑,为什么一定需要在前面加上一个强制转换语句,像是这样:int *a = (int *)malloc(sizeof(int)*3);。 为此我在菜鸟教程的malloc()函数介绍中找到了关于malloc的声明:void *malloc(size_t size),显然加上一个强制转换语句并不是标准语法必须的东西,但是在菜鸟教程下面的举例中是按照强制转换的写法来写的,可惜没有说为什么。为此我尝试了不加强制转换语句的malloc来直接分配空间,在gcc编译后并没有报错或者发出警告。 这就很神奇了,我换了多个姿势来对这两种用法进行测试,包括但不限于不同大小long *a = malloc(sizeof(int)*2)或者数组结构int *b = malloc(size(int)*3),他们都没有报错或警告,而强制转换亦是如此。 就在我怀疑是某种错误的如同void main这种异类写法产生了曼德拉效应时,我看到了另一篇博客的记录:在ANSI/ISO标准C下,我们是可以不使用强制转换来直接使用malloc的,并且使用强制转换还可能掩盖malloc()声明错误时产生的重要警告,反而不如直接使用malloc。但是使用malloc强制转换的好处在于,可以更方便地移植到C++中,因为C++似乎并不支持这种隐式转换。 根据这个帖子我大概猜测了一下国内这种喜欢在malloc函数前加强制转换命令的原因,除了个别学校在教学生C语言的时候把C/C++混为一谈,导致学生用C++的语法来理解C语言(这种情况真的不少),还有很大原因是因为国内高校很喜欢用Dev-C++这类常年未更新用着远古标准的IDE。

December 26, 2020 · 云雾海

C的main函数解析

终于开始学习Linux的C语言编程了嘤嘤嘤,有种终于入门的感动 关于Linux中C语言编程包括vim、gcc、makefile这些工具的用法这些不是本次的主题,我在这里就不详细展开了,本文只阐述一下main函数的参数调用问题。 工具 基本原理 实现与解析 工具 VirtualBox 6.1.12,Ubuntu 20.04,Code::Blocks,C 基本原理 众所周知,main函数对于系统来说其实也只是一个普通的函数,它作为一个接口与系统进行连接,每次系统调用main函数生成的程序文件。而Linux系统其实又是通过C语言写出来的,本质上其实也只是一个C程序。而调用C生成的程序文件其实就等同于将Linux的一个程序运行指针调用这个程序的地址,然后这个程序就执行起来了,而我们可以通过main函数的两个参数接口在调用这个程序的时候进行传参。 实现与解析 // Hello.c,用于测试main参数传递 #include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { printf("程序地址为:%s\n", argv[0]); printf("本次传入参数共有%d个\n", argc - 1); int i = 1; while(i < argc) // 通过循环遍历传入所有参数 { switch(argv[i][0]) // 通过首字母进行快速定位 { case 'w': case 'W': { if(strcmp(argv[i], "World") == 0) { printf("Hello World!\n"); break; } else printf("Is W/w but not World~\tHello %s\n", argv[i]); // 是W/w但是并非World break; } case 'l': case 'L': { if(strcmp(argv[i], "Linux") == 0) { printf("Hello Linux!\n"); break; } else printf("Is W/w but not World~\tHello %s\n", argv[i]); // 是L/l但是并非Liunx break; } default: printf("This is %s\n", argv[i]); break; } i++ } return 0; } 这里我们可以看到main函数中有两个参数,分别是int类型的argc和char**类型的argv两个参数,当然这两个参数的名字可以变换,就像普通函数的形参一样,但是通常大家都用这两个,相当于约定俗成了。可以看出argv是一个指向char数组的指针数组的数组指针,听上去有点绕,但是用char * argv[]应该就比较好理解了,或者说甚至可以直接当成一个二维数组,其中第一维是一堆地址,每个地址分别指向一个char数组的首地址,而char数组则构成了第二维。而argc则记录了其中第一维的指针argv[]的个数。 ...

December 20, 2020 · 云雾海

通过报文对嵌入式进行编程举例

这是对我之前《如何假装用C语言为嵌入式写一个新语言》进行一个举例,以便更好地理解这种编程方式。 报头(S)报尾(E) 控制指令 程序逻辑指令 举例 报头(S)报尾(E) 报头和报尾 ASCII BUGUS 42 55 47 55 53 BUGUE 42 55 47 55 45 CODES 43 4f 44 45 53 CODEE 43 4f 44 45 45 VORCS 56 4f 52 43 53 VORCE 56 4f 52 43 45 CONSTS 43 4f 4e 53 54 53 CONSTE 43 4f 4e 53 54 45 RVARS 52 56 41 52 53 RVARE 52 56 41 52 45 SAVES 53 41 56 45 53 SAVEE 53 41 56 45 45 WVARS 57 56 41 52 53 WVARE 57 56 41 52 45 控制指令 指令内容 指令标号(用户不可见) 参数1 参数2 输入输出选择(基础IO模块) 0x02 引脚选择(暂时0x01-0x0a) 输入0输出1 输出高低选择(基础IO模块) 0x03 引脚选择(暂时0x01-0x0a) 低电平0高电平1 毫秒级延时(时序模块) 0x04 延时时间(暂时0x01-0xff) 程序逻辑指令 指令内容 指令标号 信息1 信息2 信息3 信息4 信息5 信息6 循环启动器(程序基础控制模块) 0x01 CODE返回位 VORC返回位 CONST返回位 RVAR返回位 SAVE返回位 WVAR返回位 举例 加入用户输入了这样一个程序: ...

December 1, 2020 · 云雾海

如何假装用C语言为嵌入式写一个新语言

我们在使用无线嵌入式器件的时候,或多或少对于AT指令都有所了解——通过通信口发送一串字符,以AT开头,换行符结尾,中间插入指令内容,就可以实现对芯片寄存器的配置。这种指令式配置类似于蓝牙、WiFi等设备可能比较简单,但是如果我们打算用它去实现一个比较复杂的编程呢?是否也可以通过这种方式,是否可以在不使用编译器的情况下,通过指令调取内部程序并执行?我将会通过一个小demo进行测试这种编程方式的可行性。 本文的程序可以在这里下载 使用工具 工作流程 指令程序设计 功能映射并单指令运行 多指令运行 上位机编程 传递参数 返回值 思路解析 参数 返回值 变量和常量 输入判断 输出判断 报文 总结和反思 使用工具 Code::Blocks, C语言 工作流程 整个系统的工作流程比较简单,如下图所示: graph LR Send[报文发送端] --通信--> ReCV subgraph 单片机 ReCV[报文接收端]-->Mem[存储] Mem-->Read[读取指令] Read--函数和参数-->Work[完成操作] Work--返回值-->Mem end 这里的报文内容其实来自于上位机对下位机内部已有程序的映射,例如我在单片机中已经设置好了一个函数void Exp(),那么我们就可以在上位机制作一个模块,使用者如果调取了这个模块那么上位机就会自动将该模块对应的指令值插入到报文中,假设这里是0x05,那么下位机如果在报文中检测到了0x05,就会自动执行Exp()函数。 当然详细内容会更复杂,我们现在只是有了一个基本概念,还需要继续完善内容。 指令程序设计 如果我们需要实现指令编程,那么内部肯定不止有Exp()一个程序,先不考虑指针传参的情况,我们根据参数和返回值的不同情况可以将函数分为以下几种情况: 无参数无返回值 无参数有返回值 有参数无返回值 有参数有返回值 而实际上参数的个数不定,我们暂时先不考虑不定参数的情况,可以分为: 单个参数 多个参数 而实际上多个参数的情况可以包含单个参数,那么我们就在这个demo中设置如下几种情况。 无参数无返回值函数 无参数有返回值函数 两个参数无返回值函数 三个参数有返回值函数 为此,我设计了如下几个函数: /* * 全局变量声明 */ int static flag = 0; /* * 函数声明和定义 */ void SayHello() // 打印一行Hello World! { printf("Hello World!\n"); } int NowFlag() // 将flag值自增一然后打印出来并返回 { flag++; printf("flag = %d\n", flag); return flag; } void ShowSum(int a, int b) // 打印两个参数之和 { printf("%d + %d = %d\n", a, b, a+b); } int GetSum(int a, int b, int c) // 打印并返回三参数之和 { int sum = a + b + c; printf("%d + %d + %d = %d\n",a, b, c, sum); return sum; } 现在我们在main中测试调用他们并运行一下: ...

November 24, 2020 · 云雾海

C和C++在参数调用上的区别

在学习 C 语言的数据结构时,我发现教科书上出现的一些代码实际上并不能很好地在 C 语言环境中运行,而需要改成 C++才可以,在网上搜了一下,这里记录 C 和 C++在函数传参上的区别 C++的形参有三种写法: void example_CPP(&a, *b, c){ /*a为引用传参*/ /*b为指针传参*/ /*c为直接传参*/ } 三种写法各有用处,这里我们以实例进行研究—— 首先我们打开自己的 IDE,我用的 CodeBlocks,然后输入一下程序: #include <iostream> using namespace std; void example_CPP(int &a,int *b,int c); int main(){ /*定义三个变量并输出其值和地址*/ int x,y,z; x = y = z = 0; example_CPP(x, &y, z); cout<<"x的值:"<<x<<"\nx的地址"<<&x<<"\n"; cout<<"y的值:"<<y<<"\ny的地址"<<&y<<"\n"; cout<<"z的值:"<<z<<"\nz的地址"<<&z<<"\n"; } void example_CPP(int &a,int *b,int c){ /*将3个数分别加一*/ a += 1; *b += 1; c += 1; /*a为引用传参,传的是引用值*/ cout<<"a的值:"<<a<<"\na的地址"<<&a<<"\n"; /*b为指针传参,传的是地址*/ cout<<"b的值:"<<*b<<"\nb的地址"<<b<<"\n"; /*c为直接传参,传的是实际值*/ cout<<"c的值:"<<c<<"\nc的地址"<<&c<<"\n"; } 输出结果如下: 当我们使用直接传参时,实际过程可以理解为我们把原来 z 的值赋值给一个新的变量 c,在对 c 进行了一堆操作后释放 c 的空间,而 z 实际上并没有参与到函数中,而仅仅起到一个赋初值的作用。 而当我们进行指针传递时,可以理解为我们把 y 的地址赋值给一个指针变量 b,而通过指针 b 对其指向的 y 的存储空间进行操作,我们没有直接对 y 进行操作,但是通过指针 b 我们可以间接修改 y 的值。 ...

November 8, 2020 · 云雾海