目录
const 到底做了些什么?为什么 const 变量不能被写?
使用 void*方式传递内存块时,如何规定好收发方之间的协议(数据类型)。
核心转储(core dump)VS 段错误(Segmentation Fault)VS 各种内存问题。请举例
C 语言有多少个关键字,分别都有什么用途?
C90:32个关键字详解,见下方链接
C99新增五个关键字:inline, restric, _Bool, _Imaginary, _Complex
_Bool, _Imaginary, _Complex关键字解析
inline关键字解析:
inline用于建议编译器将函数内联展开,而不是进行常规的函数调用。
// 内联函数声明
inline int max(int a, int b);
// 内联函数定义
inline int max(int a, int b) {
return (a > b) ? a : b;
}
// 使用示例
int main() {
int x = 5, y = 10;
int result = max(x, y); // 可能被展开为:int result = (x > y) ? x : y;
return 0;
}
restric关键字解析:
restrict是一个指针限定符,告诉编译器该指针是访问其所指对象的唯一方式。
// 错误使用 - 违反restrict约定
void wrong_usage(int *restrict a, int *restrict b) {
// 如果调用时a和b指向相同内存,行为未定义
a[0] = 10;
b[0] = 20; // 违反restrict约定!
}
// 正确的方式
void independent_operations(int *restrict a, int *restrict b) {
// a和b操作完全独立
for (int i = 0; i < 100; i++) {
a[i] = i;
}
for (int i = 0; i < 100; i++) {
b[i] = i * 2;
}
}
C 语言的工作机制.
嵌入式开发学习——了解c语言运行机制和变量概念(c语言)_c语言的运行机制-优快云博客
-
预处理阶段:处理
#include指令,包含头文件;展开宏定义,将PI替换为3.14159;处理条件编译指令(#ifdef,#ifndef等) - 编译阶段:将预处理后的代码编译为汇编代码
-
汇编阶段:将汇编代码转换为目标文件;将汇编指令转换为机器码;生成重定位表、符号表
-
链接阶段:将多个目标文件链接为可执行文件
volitile 关键字的作用以及什么时候使用?
volatile int flag = 0;
// 没有volatile时,编译器可能优化为:
while(flag == 0) {
// 可能被优化为死循环,因为编译器认为flag不会改变
}
// 有volatile时,每次都会从内存读取flag的值
使用场景
场景1:硬件寄存器
// 内存映射的硬件寄存器
volatile unsigned int *status_reg = (unsigned int*)0x1000;
volatile unsigned int *data_reg = (unsigned int*)0x1004;
void wait_for_data() {
while((*status_reg & 0x01) == 0) {
// 等待数据就绪,必须从硬件读取最新状态
}
unsigned int data = *data_reg;
}
场景2:多线程共享变量
#include <pthread.h>
volatile int shared_counter = 0;
void* thread_func(void* arg) {
for(int i = 0; i < 1000; i++) {
shared_counter++; // 注意:这不是原子操作!
}
return NULL;
}
场景3:信号处理
#include <signal.h>
volatile sig_atomic_t signal_received = 0;
void signal_handler(int sig) {
signal_received = 1;
}
int main() {
signal(SIGINT, signal_handler);
while(!signal_received) {
// 正常工作,信号处理会修改signal_received
}
printf("Signal received, exiting...\n");
return 0;
}
结构体和共用体相互嵌套。
共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。您可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。
结构体嵌套共用体
// 学生信息,根据类型存储不同数据
struct student {
int id;
char name[20];
int type; // 1:本科生 2:研究生
union {
struct {
char class_name[10]; // 本科生班级
float gpa; // 本科生GPA
} undergraduate;
struct {
char research_area[30]; // 研究生研究方向
char supervisor[20]; // 导师
} graduate;
} info;
};
// 使用示例
struct student s1;
s1.type = 1; // 本科生
strcpy(s1.info.undergraduate.class_name, "CS101");
s1.info.undergraduate.gpa = 3.8;
struct student s2;
s2.type = 2; // 研究生
strcpy(s2.info.graduate.research_area, "Artificial Intelligence");
共用体嵌套结构体
// 数据包解析,共用体根据类型解析不同结构
union data_packet {
struct {
unsigned char type;
unsigned char length;
unsigned char data[256];
} raw;
struct {
unsigned char type; // 必须为1
unsigned char length; // 必须为8
unsigned int temperature;
unsigned int humidity;
} sensor_data;
struct {
unsigned char type; // 必须为2
unsigned char length; // 必须为4
unsigned short x;
unsigned short y;
} position_data;
};
// 使用示例
union data_packet packet;
memcpy(&packet, received_data, sizeof(packet));
switch(packet.raw.type) {
case 1:
printf("Temperature: %u, Humidity: %u\n",
packet.sensor_data.temperature,
packet.sensor_data.humidity);
break;
case 2:
printf("Position: (%u, %u)\n",
packet.position_data.x,
packet.position_data.y);
break;
default:
printf("Unknown packet type\n");
}
位结构体是什么。
基本概念:位结构体允许按位分配结构体成员的内存空间,用于节省内存和硬件寄存器操作。
基本语法
struct bit_field {
type member_name : width;
};
实际应用示例
示例1:状态寄存器
// 32位状态寄存器位域定义
struct status_register {
unsigned int error_flag : 1; // 第0位:错误标志
unsigned int data_ready : 1; // 第1位:数据就绪
unsigned int busy : 1; // 第2位:忙标志
unsigned int reserved : 5; // 第3-7位:保留
unsigned int mode : 3; // 第8-10位:模式选择
unsigned int channel : 4; // 第11-14位:通道选择
unsigned int : 17; // 第15-31位:未命名,填充对齐
};
// 使用示例
struct status_register status;
status.error_flag = 0;
status.data_ready = 1;
status.mode = 5; // 二进制101
status.channel = 12; // 二进制1100
printf("Register value: 0x%08X\n", *(unsigned int*)&status);
示例2:IP协议头
// IP协议头位域定义
struct ip_header {
unsigned int version : 4; // 版本号
unsigned int ihl : 4; // 头部长度
unsigned int tos : 8; // 服务类型
unsigned int total_length : 16; // 总长度
unsigned int identification : 16; // 标识
unsigned int flags : 3; // 标志位
unsigned int fragment_offset : 13; // 分片偏移
unsigned int ttl : 8; // 生存时间
unsigned int protocol : 8; // 协议类型
unsigned int checksum : 16; // 头部校验和
unsigned int src_addr; // 源IP地址
unsigned int dst_addr; // 目的IP地址
};
示例3:紧凑数据存储
// 紧凑存储个人基本信息
struct personal_info {
unsigned int age : 7; // 0-127岁
unsigned int gender : 1; // 0:男, 1:女
unsigned int education : 3; // 学历: 0-7
unsigned int married : 1; // 婚姻状况
unsigned int has_car : 1; // 是否有车
unsigned int has_house : 1; // 是否有房
unsigned int : 18; // 保留位
};
// 使用示例
struct personal_info person;
person.age = 25;
person.gender = 0; // 男
person.education = 4; // 本科
person.married = 0;
person.has_car = 1;
person.has_house = 0;
printf("Size of personal_info: %zu bytes\n", sizeof(person));
// 输出:4 bytes(而不是可能的更多字节)
在 C 语言的编译体系中,RAM 被怎样划分了?
C语言:内存分配---栈区、堆区、全局区、常量区和代码区_c 内存布局-优快云博客

指针的作用?
函数参数传递,到底传了些什么给被调函数?
什么是回调函数?
回调函数就是一个通过函数指针调用的函数。
const 到底做了些什么?为什么 const 变量不能被写?
全局变量的利与弊,如何控制全局变量?
全局变量的利:
-
方便访问:全局变量可以在程序的任何地方被访问,这使得在多个函数或模块间共享数据变得容易。
-
减少参数传递:使用全局变量可以减少函数间参数传递的数量,简化函数调用。
-
保持状态:全局变量可以用来保持程序的状态,如配置设置或用户偏好。
全局变量的弊:
-
增加耦合度:全局变量增加了模块间的耦合度,使得代码更难理解和维护。
-
命名空间污染:全局变量可能导致命名空间污染,增加命名冲突的可能性。
-
调试困难:全局变量可以在程序的任何地方被修改,这使得调试变得困难,因为很难追踪变量的修改来源。
-
线程安全问题:在多线程环境中,全局变量可能导致线程安全问题,如竞态条件和数据不一致。
-
测试难度增加:全局变量使得单元测试变得更加困难,因为测试需要考虑到全局状态的影响。
如何控制全局变量:
-
限制使用:尽量减少全局变量的使用,只在确实需要时才使用。
-
命名约定:使用明确的命名约定来标识全局变量,如使用
g_前缀。 -
封装:将全局变量封装在模块或类中,通过接口访问,这样可以控制对全局变量的访问和修改。
-
使用配置文件:对于配置设置等全局变量,可以使用配置文件来管理,而不是直接在代码中定义。
-
单例模式:对于需要全局访问的对象,可以使用单例模式来控制实例的创建和访问。
-
线程同步:在多线程环境中,使用同步机制(如互斥锁)来保护全局变量,避免线程安全问题。
-
文档记录:对所有全局变量进行详细的文档记录,说明其用途、生命周期和修改来源。
-
代码审查:通过代码审查来监控全局变量的使用,确保它们被合理地使用和管理。
控制全局变量的关键在于谨慎使用,并通过封装、同步和文档等手段来减少它们的负面影响。
预处理究竟干了些什么?为什么它如此不负责?
#define SQUARE(x) x*x
int a = SQUARE(1+2); // 预处理产出:int a = 1+2*1+2;
预处理器不会帮你加括号,也不会报错,责任后移到编译阶段甚至运行阶段。
设计初衷就是“最快地把源文件剪贴成一份纯 C 代码”,而不是“懂你写啥”。
初始化到底有什么作用?怎样才是有效的初始化?
把对象(变量、数组、结构体、联合体…)在正式使用前置成确定值,避免:
-
读到垃圾值(未定义行为)
-
重复赋值带来的性能/逻辑隐患
-
静态存储期变量默认零初始化以外的意外
数组和指针的关系?
头文件之间的包含关系。
如何在嵌入式编程中使用位运算?请举例
如何善用 typedef 创造有效的移植代码体系。
// portable_types.h
#ifndef PORTABLE_TYPES_H
#define PORTABLE_TYPES_H
#include <stdint.h>
// 明确大小的整数类型
typedef int8_t s8;
typedef uint8_t u8;
typedef int16_t s16;
typedef uint16_t u16;
typedef int32_t s32;
typedef uint32_t u32;
typedef int64_t s64;
typedef uint64_t u64;
// 平台相关的通用类型(基于架构自动选择)
#if defined(__x86_64__) || defined(_M_X64)
typedef int64_t intptr;
typedef uint64_t uintptr;
typedef int64_t ssize_t;
#else
typedef int32_t intptr;
typedef uint32_t uintptr;
typedef int32_t ssize_t;
#endif
// 布尔类型(确保C和C++兼容)
#ifdef __cplusplus
typedef bool bool_t;
#else
typedef _Bool bool_t;
#ifndef true
#define true 1
#endif
#ifndef false
#define false 0
#endif
#endif
#endif // PORTABLE_TYPES_H
使用 void*方式传递内存块时,如何规定好收发方之间的协议(数据类型)。
如何处理常见编译错误的能力。
处理常见编译警告的能力。编译警告清零的能力。
常见的 C 语言的陷阱和缺陷有哪些?
1. 内存管理陷阱:内存泄漏、悬空指针、双重释放;
2.数组和指针陷阱:数组越界,指针算数错误
如何掌握防御性编程技巧。
防御性编程是一种重要的编程哲学,旨在使代码在遇到意外情况时仍能保持稳定、安全和可预测的行为。
基本原则
-
不信任任何输入:包括用户输入、文件数据、网络数据、函数参数
-
明确处理所有错误:不要忽略返回值或错误码
-
设计容错系统:系统在部分故障时仍能继续运行
-
保持代码简洁清晰:复杂代码更容易隐藏错误
成对编码原则的贯彻是有效编码的基础。
成对编码原则 是指在编程中,某些操作必须成对出现、成对管理、成对释放的编码规范。这个原则强调资源的对称性管理,确保每个创建操作都有对应的销毁操作,每个获取操作都有对应的释放操作。
糟糕的代码风格 VS 优秀代码风格。请举例
核心转储(core dump)VS 段错误(Segmentation Fault)VS 各种内存问题。请举例
段错误是操作系统内存保护机制触发的错误,当程序试图访问其没有权限访问的内存区域时发生。

内存违规访问
↓
段错误 (Segmentation Fault) - 信号SIGSEGV
↓
核心转储 (Core Dump) - 程序状态的快照
↓
调试分析
什么是内嵌汇编,什么时候使用内嵌汇编块。
内嵌汇编(Inline Assembly)是在高级语言(主要是C/C++)代码中直接嵌入汇编指令的技术。它允许开发者在高级语言环境中使用低级的汇编指令,结合两者的优势。z主要目的:1. 性能关键代码优化;
#include <stdint.h>
// 使用内嵌汇编优化内存拷贝
void fast_memcpy(void* dest, const void* src, size_t n) {
if (n == 0) return;
// 对于小数据使用内嵌汇编可能更快
asm volatile (
"cld\n\t" // 清除方向标志(向前复制)
"rep movsb" // 重复移动字节
: "+D" (dest), "+S" (src), "+c" (n)
:
: "memory" // 破坏内存内容
);
}
// 优化的字符串长度计算
size_t fast_strlen(const char* str) {
size_t len;
asm volatile (
"xor %0, %0\n\t" // 清零长度计数器
"1:\n\t"
"cmpb $0, (%1, %0)\n\t" // 检查当前字符是否为0
"je 2f\n\t"
"inc %0\n\t" // 长度加1
"jmp 1b\n\t" // 循环
"2:"
: "=&r" (len) // 早期破坏约束
: "r" (str)
: "cc", "memory" // 破坏条件码和内存
);
return len;
}
2.访问特殊硬件功能
// 读取时间戳计数器(用于高精度计时)
static inline uint64_t read_tsc() {
uint32_t lo, hi;
// RDTSC指令读取时间戳计数器
asm volatile (
"rdtsc"
: "=a" (lo), "=d" (hi) // 输出到EAX和EDX
);
return ((uint64_t)hi << 32) | lo;
}
// 控制CR0寄存器(系统级操作)
void enable_write_protect() {
unsigned long cr0;
asm volatile (
"mov %%cr0, %0\n\t" // 读取CR0
"or $0x10000, %0\n\t" // 设置写保护位
"mov %0, %%cr0" // 写回CR0
: "=r" (cr0)
:
: "memory"
);
}

被折叠的 条评论
为什么被折叠?



