嵌入式软件开发工程师如何提高C语言编码技能?
liebian365 2024-12-31 12:45 16 浏览 0 评论
编者根据多年的嵌入式C语言编程经验,这里不再针对C语言基础,希望你已经有了一定的C语言编程基础,总结提炼了一些入门后精进的C语言常用的编程技巧,既是对自己的回顾,也帮助大家一起来有重点的理解嵌入式linux C语言编程。
本文把linux C语言按照使用经验,分为三类展开:第一类是单独的C标准编程;第二类是C语言系统编程;第二类是C语言调试。
无偿分享大家一个资料包,差不多150多G。里面学习内容、面经、项目都比较新也比较全!某鱼上买估计至少要好几十。
一、C语言编程
1.熟悉编程规范: 遵循一致的编程风格和规范,可以使代码更易于阅读和维护。可以参考一些知名的编程规范,如GNU、Google、LLVM等。
2.熟练预处理用法:预处理器可以用于定义宏,包含其他文件,条件编译等。以下是一些基本的预处理器用法:
#define PI 3.14159 // 定义常量
#include "myheader.h" // 包含其他文件
#ifdef DEBUG // 条件编译
printf("Debug info: x = %d\n", x);
#endif
3.理解内存管理: C语言不会自动管理内存,因此理解如何有效地使用malloc、calloc、realloc和free来管理内存是非常重要的。记得检查动态内存分配的返回值以防止内存分配失败,并确保适当地释放任何分配的内存以防止内存泄漏。
C语言中,我们需要手动进行内存分配和释放。这是一个使用malloc和free进行内存管理的简单例子:
#include <stdlib.h>
int *array = (int*) malloc(10 * sizeof(int)); // 分配空间
if(array == NULL) { // 检查内存分配是否成功
// 处理错误
}
// ... 使用数组
free(array); // 释放空间
array = NULL; // 避免使用悬空指针
4.使用模块和抽象: 尽可能地将代码组织成独立的、可重用的模块。这不仅使代码更易于理解和维护,而且还可以提高代码复用率。创建抽象的数据类型和函数可以隐藏实现细节,使代码更简洁。
假设我们想要创建一个表示“点”(Point)的模块。我们可以在point.h头文件中定义一个结构体和相关的函数:
// point.h
typedef struct {
double x, y;
} Point;
void movePoint(Point *p, double dx, double dy);
double distanceToOrigin(Point *p);
然后在point.c文件中实现这些函数:
// point.c
#include "point.h"
#include <math.h>
void movePoint(Point *p, double dx, double dy) {
p->x += dx;
p->y += dy;
}
double distanceToOrigin(Point *p) {
return sqrt(p->x * p->x + p->y * p->y);
}
5.错误处理: C语言并没有内置的异常处理机制,所以建议使用返回值来表示函数是否执行成功,以及使用全局变量或者指针参数来返回错误信息。使用assert宏来捕获不应该发生的情况也是一个好的编程习惯。
在C语言中,我们通常使用返回值来表示错误。以下是一个简单的错误处理的例子:
#include <stdio.h>
int divide(int a, int b, int *result) {
if (b == 0) {
return -1; // 返回错误代码
}
*result = a / b;
return 0; // 表示成功
}
int main() {
int result;
if (divide(10, 2, &result) == 0) {
printf("The result is %d\n", result);
} else {
printf("An error occurred!\n");
}
return 0;
}
6.使用指针和结构体: 通过熟练掌握指针和结构体,可以创建复杂的数据结构,如链表、树和图等。理解指针算术和多级指针是必不可少的。
使用指针和结构体,我们可以创建复杂的数据结构,如链表。这是一个简单的单链表的例子:
typedef struct Node {
int value;
struct Node *next;
} Node;
Node *createNode(int value) {
Node *node = (Node*) malloc(sizeof(Node));
node->value = value;
node->next = NULL;
return node;
}
void appendNode(Node **head, int value) {
Node *newNode = createNode(value);
if (*head == NULL) {
*head = newNode;
} else {
Node *current = *head;
while (current->next != NULL) {
current = current->next;
}
current->next = newNode;
}
}
7.位操作: C语言提供了一系列的位操作符,这些操作符可以用来实现某些高效的算法和数据结构。例如,可以使用位字段来节省存储空间,使用位掩码来设置和检查某些特定的位,等等。
位操作常用于处理二进制数据。以下是一些基本的位操作例子:
unsigned int a = 60; // 60 = 0011 1100
unsigned int b = 13; // 13 = 0000 1101
int result;
result = a & b; // Bitwise AND 按位与操作
result = a | b; // Bitwise OR 按位或操作
result = a ^ b; // Bitwise XOR 按位异或操作
result = ~a; // Bitwise NOT 按位非操作
result = a << 2; // Left shift 左移操作
result = a >> 2; // Right shift 右移操作
8.理解C语言的“未定义行为”: C语言标准中有很多“未定义行为”,这意味着程序可能会有一些出乎意料的结果。理解并避免这些未定义行为是写出可靠和安全代码的关键。
以下是一个触发未定义行为的例子:
int x = 1;
printf("%d %d\n", x, x++); // 在同一个表达式中,x的值被修改了一次以上,结果是未定义的
9.熟悉C的标准库: C语言的标准库包含了许多有用的函数,如字符串操作、数学函数、时间和日期函数等等。熟悉这些库函数可以提高编程效率,而且这些库函数通常比手写的代码更优化和更可靠。
C语言的标准库有许多有用的函数,比如strcpy,atoi,sqrt等。这是一个例子:
#include <string.h>
#include <stdlib.h>
#include <math.h>
int main() {
char str[10];
strcpy(str, "123"); // 字符串复制
int i = atoi(str); // 字符串转整型
double s = sqrt(i); // 开方
return 0;
}
10.使用函数指针: 函数指针可以被用作回调函数,以实现事件驱动编程或者自定义算法的行为。函数指针也可以用来实现类似于面向对象编程中的虚函数的功能。
11.理解数据对齐: 数据对齐对于提高内存访问的性能和准确性很重要。理解如何使用关键字alignas和alignof,以及如何使用结构体填充来保证数据的对齐。
数据对齐可以帮助我们提高内存访问的效率。这是一个例子:
struct Data {
char c; // 1 byte
int i; // 4 bytes
};
由于大多数系统中,整型需要按4字节对齐,所以在c和i之间,编译器会插入3字节的填充。
二、C语言系统编程
1.信号处理: 理解如何使用信号处理函数来响应和处理操作系统发送给你的程序的信号。但是需要注意,信号处理函数只能使用一些异步安全的函数。
在C语言中,我们可以使用signal函数来处理操作系统发送给我们的信号。这是一个简单的例子:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handle_sigint(int sig) {
printf("Caught signal %d\n", sig);
}
int main() {
signal(SIGINT, handle_sigint);
while (1) {
sleep(1);
}
return 0;
}
运行这个程序,然后按Ctrl+C,你会看到它打印出"Cought signal 2"。
2.并发编程: 使用多线程可以提高程序的性能和响应性。C11标准引入了一些关于多线程的新特性。理解线程间的数据共享、同步和通信是关键。但是需要注意,不正确的并发编程可能会导致诸如竞态条件、死锁和数据不一致等问题。
3.系统编程: 熟练掌握如何使用系统调用,如文件I/O、网络编程、进程管理、信号处理等。理解阻塞和非阻塞I/O,以及如何使用select、poll和epoll进行高效的事件驱动编程。
char str[100];
fgets(str, sizeof(str), stdin); // 使用fgets代替gets,避免缓冲区溢出
4.掌握文件I/O:
C语言中使用fopen,fread/fwrite,fscanf/fprintf,fclose等函数进行文件操作。这是一个将数据写入文件的例子:
FILE *file = fopen("test.txt", "w");
if (file != NULL) {
fprintf(file, "Hello, World!\n");
fclose(file);
}
5.多线程编程:
C语言可以使用POSIX线程(pthreads)库来实现多线程编程。下面是一个简单的示例:
#include <pthread.h>
#include <stdio.h>
void* print_message_function(void* ptr) {
char* message;
message = (char*) ptr;
printf("%s \n", message);
}
?
int main() {
pthread_t thread1, thread2;
char* message1 = "Thread 1";
char* message2 = "Thread 2";
?
pthread_create(&thread1, NULL, print_message_function, (void*) message1);
pthread_create(&thread2, NULL, print_message_function, (void*) message2);
?
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
?
return 0;
}
三、C语言调试
1.内联函数和宏: 内联函数和宏都可以用来提高程序的运行速度,但需要谨慎使用。过度使用可能会导致代码的可读性和可维护性降低,而且可能不会真正提高程序的性能。
inline int max(int a, int b) {
return a > b ? a : b;
}
2.优化编译器设置: 大多数现代编译器提供了一系列的优化选项,通过合理地设置这些选项,可以提高程序的性能。但是在开启某些优化选项时需要注意,可能会改变代码的语义或者引入一些难以调试的问题。
3.掌握调试工具: 熟练掌握像GDB这样的调试工具,以及如何使用valgrind来检查内存泄漏和其他运行时错误。使用gprof或其他性能分析工具来找出程序的瓶颈并进行优化。
GDB是一个常用的C语言调试工具。以下是一些基本的GDB命令:
gcc -g myprogram.c // 使用-g选项来生成调试信息
gdb a.out // 使用GDB启动程序
break main // 在main函数处设置断点
run // 运行程序
next // 执行下一行代码
print x // 打印变量x的值
quit // 退出GDB
4.使用动态库: 动态链接库可以在运行时被加载和卸载,这使得程序可以在运行时扩展功能,并且节省磁盘和内存空间。理解如何创建和使用动态库是高级C编程的重要部分。
C语言中,我们可以创建和使用动态库(也叫共享库)。这是一个简单的创建动态库的例子:
// libhello.c
#include <stdio.h>
?
void hello() {
printf("Hello, World!\n");
}
然后,我们可以用下面的命令创建一个动态库:
gcc -shared -o libhello.so libhello.c
这样,我们就可以在其他程序中使用这个库了:
// main.c
void hello();
?
int main() {
hello();
return 0;
}
编译时,我们需要链接这个动态库:
shell
gcc -L. -lhello -o main main.c
5.使用条件编译: 条件编译(例如#ifdef、#ifndef和#if等预处理命令)可以根据特定的编译选项或环境变量来改变代码的编译结果。这可以用来创建可以在多种环境或平台下编译和运行的代码。
条件编译可以让我们根据编译时的条件来改变代码。这是一个简单的例子:
#include <stdio.h>
?
int main() {
#ifdef DEBUG
printf("Debug mode\n");
#else
printf("Normal mode\n");
#endif
return 0;
}
6.理解和使用编译器警告: 编译器警告可以帮助你发现代码中可能的问题。理解并重视这些警告,尽可能地消除所有的警告。
编译器的警告可以帮助我们找出代码中可能的问题。我们应该尽可能地开启并处理所有警告。这是一个例子:
int main() {
int i;
printf("%d\n", i); // 使用未初始化的变量,编译器会给出警告
return 0;
}
我们可以使用-Wall选项来开启所有的警告:
gcc -Wall myprogram.c
7.注重安全编程: 特别是在编写网络程序或处理用户输入时,需要防止缓冲区溢出、注入攻击等安全问题。理解如何使用安全的函数,如snprintf代替sprintf,strncpy代替strcpy等。
在处理用户的输入时,我们需要注意防止缓冲区溢出。以下是一个安全的读取字符串的例子:
char str[100];
fgets(str, sizeof(str), stdin); // 使用fgets代替gets,避免缓冲区溢出
相关推荐
- 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字符串复制...
- 二年级上册语文必考句子仿写,家长打印,孩子照着练
-
二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- 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)