cms 导航网站,中国空间站研究项目,宁波网站推广外包服务,电商设计灵感网站针对于嵌入式软件杂乱的知识点总结起来#xff0c;提供给读者学习复习对下述内容的强化。 目录
1.C语言宏中#“和##的用法
1.1.(#)字符串化操作符
1.2.(##)符号连接操作符
2.关键字volatile有什么含意?并举出三个不同的例子?
2.1.并行设备的硬件寄存… 针对于嵌入式软件杂乱的知识点总结起来提供给读者学习复习对下述内容的强化。 目录
1.C语言宏中#“和##的用法
1.1.(#)字符串化操作符
1.2.(##)符号连接操作符
2.关键字volatile有什么含意?并举出三个不同的例子?
2.1.并行设备的硬件寄存器
2.2.中断服务程序中修改的变量
2.3.多线程中共享的变量
3.关键字static的作用是什么?
3.1.在函数体内定义静态变量
3.2.在模块内定义静态变量
3.3.在模块内定义静态函数
4.在C语言中为什么 static 变量只初始化一次?
5.externc”的作用是什么?
6.const有什么作用?
6.1.定义变量为常量
6.2.修饰函数的参数
6.3.修饰函数的返回值
6.4.节省空间避免不必要的内存分配
7.什么情况下使用const关键字?
8.new/delete与malloc/free的区别是什么?
8.1.类型安全性
8.2.构造函数与析构函数
8.3.内存管理
8.4.对象的内存对齐和初始化
9.strlen(\0)? sizeof(\0)?
10.sizeof和strlen有什么区别?
11.不使用 sizeof如何求int占用的字节数?
12.C语言中 struct与 union的区别是什么?
13.左值和右值是什么?
14.什么是短路求值?
15.a和a有什么区别?两者是如何实现的? 1.C语言宏中#“和##的用法
1.1.(#)字符串化操作符
功能将宏参数转换为字符串字面量。
用法# 操作符会将紧随其后的参数转换为一个带双引号的字符串。
#include stdio.h
#define STR(x) #x
int main() {printf(%s\n, STR(Hello, World!)); // 输出Hello, World!printf(%s\n, STR(123)); // 输出123return 0;
}1.2.(##)符号连接操作符
功能将两个标记Token拼接为一个标记。
用法## 操作符会将它两边的宏参数或标记拼接在一起形成新的标记。
#include stdio.h#define CONCAT(x, y) x##yint main() {int xy 100;printf(%d\n, CONCAT(x, y)); // 输出100return 0;
}CONCAT(x, y) 将 x 和 y 拼接为 xy因此 xy 变量被正确解析。## 在生成代码时非常有用例如动态生成变量名或函数名。 2.关键字volatile有什么含意?并举出三个不同的例子?
2.1.并行设备的硬件寄存器
存储器映射的硬件寄存器通常加volatile,因为寄存器随时可以被外设硬件修改。当声明指向设备寄存器的指针时一定要用volatile它会告诉编译器不要对存储在这个地址的数据进行假设。
就比如我们常用的MDK中你单纯给一个寄存器赋值不加volatile会被优化掉程序会略过这个内容去编译别的部分。
#define XBYTE ((volatile unsigned char*)0x8000) // 假设硬件寄存器的基地址void set_register() {XBYTE[2] 0x55; // 写入 0x55XBYTE[2] 0x56; // 写入 0x56XBYTE[2] 0x57; // 写入 0x57XBYTE[2] 0x58; // 写入 0x58
}如果未声明 volatile编译器可能优化为直接写入最后的值 0x58。声明了 volatile 后编译器会逐条生成机器代码确保硬件设备能够接收到完整的写入操作序列。 2.2.中断服务程序中修改的变量
volatile提醒编译器它后面所定义的变量随时都有可能改变。因此编译后的程序每次需要存储或读取这个变量的时候都会直接从变量地址中读取数据。如果没有volatile关键字则编译器可能优化读取和存储可能暂时使用寄存器中的值如果这个变量由别的程序更新了的话将出现不一致的现象。
当中断服务程序ISR修改一个变量主程序可能在等待该变量的改变。在这种情况下使用 volatile 避免主程序读取优化后的缓存值确保从内存中获取最新值。
#include stdbool.hvolatile bool flag false; // 用于主程序和中断之间的通信void ISR() {flag true; // 中断触发时修改变量
}void main() {while (!flag) {// 等待中断触发}// 中断触发后执行其他操作
}如果未声明 volatile主程序可能会认为 flag 始终未改变从而陷入死循环。使用 volatile 后每次都会直接从内存读取 flag 的值确保中断修改可以被感知。 2.3.多线程中共享的变量
在多线程环境中不同线程可能会访问或修改同一个变量。volatile 确保每个线程都能读取到变量的最新值而不是被优化后的缓存值。
#include pthread.h
#include stdbool.hvolatile bool stop false; // 多线程共享变量void* thread_func(void* arg) {while (!stop) {// 执行线程操作}return NULL;
}int main() {pthread_t thread;pthread_create(thread, NULL, thread_func, NULL);// 主线程控制其他操作sleep(2);stop true; // 通知线程停止pthread_join(thread, NULL);return 0;
}如果未声明 volatile线程可能会读取到未更新的 stop 值导致逻辑错误。声明 volatile 后线程每次都会从内存中读取 stop 的值。 3.关键字static的作用是什么?
3.1.在函数体内定义静态变量
在函数体只会被初始化一次一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
静态变量具有静态存储周期只会被初始化一次且在函数调用结束后其值不会丢失而是保持到下一次函数调用。
#include stdio.hvoid counter() {static int count 0; // 静态变量仅初始化一次count;printf(Count %d\n, count);
}int main() {counter(); // 输出Count 1counter(); // 输出Count 2counter(); // 输出Count 3return 0;
}static 保证变量只初始化一次即使函数被多次调用。变量在函数作用域内可见但其值会在多次调用中保持。 3.2.在模块内定义静态变量
当在函数体外即全局作用域使用 static 时变量的作用域被限制在当前文件不能被其他文件中的代码访问。这种变量称为局部全局变量。 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量(只能被当前文件使用)。
// file1.c
#include stdio.hstatic int local_var 10; // 静态全局变量void print_local_var() {printf(local_var %d\n, local_var);
}// file2.c
extern void print_local_var();int main() {print_local_var(); // 如果没有 staticlocal_var 可直接被访问return 0;
}限制全局变量的作用域仅在当前文件中可见。避免命名冲突特别是在大型项目中。 3.3.在模块内定义静态函数
当函数使用 static 关键字修饰时其作用域被限制在当前文件不能被其他文件调用。这种函数被称为静态函数。
在模块内一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是这个函数被限制在声明它的模块的本地范围内使用(只能被当前文件使用)。
// file1.c
#include stdio.hstatic void local_function() {printf(This is a static function.\n);
}void call_local_function() {local_function();
}// file2.c
extern void call_local_function();int main() {call_local_function(); // 正常调用 file1.c 的接口函数// local_function(); // 错误无法访问静态函数return 0;
}限制函数作用域仅在当前文件中可见。适合用于实现模块内部的辅助功能避免函数命名冲突。
作用域static 的用途函数体内变量变量只初始化一次值在函数多次调用中保持不变。模块内变量全局变量变量作用域仅限于当前文件防止全局命名冲突。模块内函数函数作用域仅限于当前文件适用于模块内部使用的辅助功能。
注意我们很多时候是要避免各种全局变量的因此我们除了利用结构体就是利用第一点函数体内变量这个办法。 4.在C语言中为什么 static 变量只初始化一次?
静态变量 (static) 存储在内存的 静态存储区也称为 全局数据区。
对于所有的对象(不仅仅是静态对象)初始化都只有一次而由于静态变量具有“记忆“功能初始化后一直都没有被销毁都会保存在内存区域中所以不会再次初始化。存放在静态区的变量的生命周期一般比较长它与整个程序“同生死、共存亡”,所以它只需初始化一次。而auto变量即自动变量由于它存放在栈区一旦函数调用结束就会立刻被销毁。
类型存储位置生命周期初始化次数静态变量静态存储区程序运行期间始终存在1 次自动变量栈区随函数调用创建随函数结束销毁每次重新初始化 5.externc”的作用是什么?
extern℃的主要作用就是为了能够正确实现C代码调用其他C语言代码。加上externC后会指示编译器这部分代码按C语言的进行编译而不是C的。 extern C 的主要作用是实现 C 和 C 之间的兼容性 C 和 C 在函数符号名称处理上有本质区别。C 支持 函数重载因此采用了 名称修饰Name Mangling 技术使同名函数可以根据参数的类型和数量生成唯一的符号。C 不支持函数重载函数名称在编译后直接对应符号表中的函数名。如果 C 代码直接调用 C 的函数或者反之名称修饰会导致链接器无法找到正确的符号。extern C 告诉编译器关闭 C 的名称修饰按照 C 的方式处理符号表。 应用如下
// C 文件
#include example.hextern C {#include example.h
}int main() {print_message(Hello from C);return 0;
}在 C 头文件 中添加 extern C 包装避免名称修饰问题
#ifdef __cplusplus
extern C {
#endifvoid function_in_c();#ifdef __cplusplus
}
#endif仅限在 C 环境中使用 C 编译器不支持 extern C 关键字因此在混合编译时需要通过宏区分语言环境__cplusplus 宏用于判断是否是 C 编译器。 仅影响链接Linking阶段 extern C 并不改变代码的编译方式只是改变符号表的生成方式。 6.const有什么作用?
6.1.定义变量为常量
局部变量或全局变量 可以通过 const 来定义为常量一旦赋值后该常量的值就不能被修改。
const int N 100; // 定义常量N值为100
// N 50; // 错误常量的值不能被修改
const int n; // 错误常量在定义时必须初始化6.2.修饰函数的参数
使用 const 修饰函数参数表示该参数在函数体内不能被修改。这样可以保证函数不会无意间修改传入的参数值增加代码的可维护性。
void func(const int x) {// x 10; // 错误x 是常量不能修改
}6.3.修饰函数的返回值
a. 返回指针类型并使用 const 修饰
当函数返回指针时若用 const 修饰返回值类型那么返回的指针所指向的数据内容不能被修改同时该指针也只能赋值给被 const 修饰的指针。
const char* GetString() {return Hello;
}const char* str GetString(); // 正确str 被声明为 const
// char* str GetString(); // 错误str 未声明为 const不能修改返回值b. 返回普通类型并使用 const 修饰
如果 const 用于修饰普通类型的返回值如 int由于返回值是临时的副本在函数调用结束后返回值的生命周期也随之结束因此将其修饰为 const 是没有意义的。
const int GetValue() {return 5;
}int x GetValue(); // 正确返回值可以赋给普通变量
// const int y GetValue(); // 不必要的因为返回值会是临时变量不会被修改6.4.节省空间避免不必要的内存分配
const 关键字还可以帮助优化内存管理。当你使用 const 来定义常量时编译器会考虑将常量放入只读存储区避免了额外的内存分配。对于宏#define和 const 常量它们在内存分配的方式上有所不同。
#define PI 3.14159 // 使用宏定义常量 PI
const double pi 3.14159; // 使用 const 定义常量 pi使用宏定义的常量如 PI会在编译时进行文本替换所有使用该宏的地方都会被替换为常量值因此不会单独分配内存而 const 常量则会在内存中分配空间通常存储在只读数据区。
double i PI; // 编译期间进行宏替换不会分配内存
double I pi; // 分配内存存储常量 pi宏定义常量的每次使用都会进行文本替换因此会进行额外的内存分配。相反const 常量只会分配一次内存。
#define PI 3.14159 // 宏定义常量 PI
double j PI; // 这里会进行宏替换不会再次分配内存
double I PI; // 宏替换后再次分配内存7.什么情况下使用const关键字?
序号使用场景示例说明1修饰一般常量const int x 2; int const x 2;定义只读的常量const 位置灵活。2修饰常数组const int arr[8] {1,2,3,4,5,6,7,8}; int const arr[8] {1,2,3,4,5,6,7,8};定义的数组内容不可修改。3修饰常对象const A obj; A const obj;定义的类对象不可被修改且需立即初始化。4修饰指针相关- const int *p;指向常量的指针p的内容不可变p本身可变 - int *const p;指针常量p不可变内容可变 - const int *const p;指向常量的常量指针p和内容都不可变不同组合修饰指针的行为。5修饰常引用void func(const int ref);常引用绑定到变量后不能更改其指向对象的值可保护传入变量不被函数修改。6修饰函数的常参数void func(const int var);参数不可在函数体内被修改。7修饰函数的返回值- const int func();返回的值不可修改 - const A func();返回的对象不可修改表明返回值不可被外部代码修改。8跨文件使用常量extern const int i;在其他文件中使用 const 修饰的全局变量。
const 的位置
const 可以放在类型前或类型后如 const int 与 int const 表达同样的含义。
对于指针的 const 修饰其位置决定了是修饰指针本身还是指针指向的内容。
保护机制 使用 const 的核心目的之一是防止数据被意外修改提高代码的安全性和可读性。
类和对象 对象或成员函数被 const 修饰后只能调用其他 const 成员函数确保不会修改对象的状态。
通过合理使用 const可以编写更安全、健壮的代码。 8.new/delete与malloc/free的区别是什么? 8.1.类型安全性
new 和 delete
new 是 C 的运算符delete 也是运算符具有类型安全性。new 会返回正确类型的指针无需强制转换。使用时编译器会自动计算所需内存的大小。delete 会释放通过 new 分配的内存并自动调用对象的析构函数。
int* p new int; // 分配内存并返回指向 int 类型的指针
delete p; // 释放内存并调用析构函数malloc 和 free
malloc 和 free 是 C 标准库函数malloc 返回的是 void* 指针必须显式转换为实际的类型指针。它没有类型安全性容易导致错误。malloc 只是为内存分配空间并不调用构造函数而 free 只是释放内存并不调用析构函数。
int* p (int*)malloc(sizeof(int)); // 需要手动转换类型
free(p); // 只释放内存8.2.构造函数与析构函数
new 和 delete
当使用 new 分配内存时会自动调用类的构造函数来初始化对象。当使用 delete 释放内存时会自动调用类的析构函数。
class MyClass {
public:MyClass() { cout Constructor called endl; }~MyClass() { cout Destructor called endl; }
};MyClass* obj new MyClass; // 自动调用构造函数
delete obj; // 自动调用析构函数malloc 和 free
malloc 不会调用构造函数仅分配内存free 不会调用析构函数仅释放内存。
MyClass* obj (MyClass*)malloc(sizeof(MyClass)); // 不会调用构造函数
free(obj); // 不会调用析构函数8.3.内存管理
new 和 delete
new 在分配内存时会计算所需内存的大小并根据类型自动计算。delete 自动处理内存释放及相关清理工作。
malloc 和 free
malloc 需要明确指定需要分配的字节数不会考虑对象的类型。free 只能释放 malloc 或 calloc 分配的内存并且不能自动调用析构函数。
int* p (int*)malloc(10 * sizeof(int)); // 需要手动计算内存大小8.4.对象的内存对齐和初始化
new 和 delete
new 会调用类的构造函数进行初始化并且会适当地进行内存对齐。delete 会释放内存并自动调用析构函数。
malloc 和 free
malloc 只分配原始内存不会初始化对象。如果需要初始化对象必须手动进行。free 只会释放内存而不会调用析构函数。
int* p new int(5); // 自动初始化
delete p; // 自动释放并调用析构函数特性new/deletemalloc/free语言CC类型安全类型安全自动推导和转换需要手动类型转换构造函数/析构函数自动调用构造函数/析构函数不调用构造函数/析构函数内存分配自动计算内存大小需要手动指定内存大小内存初始化支持初始化不会初始化内存使用方式运算符使用 new 和 delete函数使用 malloc 和 free 9.strlen(\0)? sizeof(\0)?
strlen(\0)0 ,sizeof(\0)2。 strlen用来计算字符串的长度(在C/C中,字符串是以0作为结束符的)它从内存的某个位置(可以是字符串开头中间某个位置甚至是某个不确定的内存区域)开始扫描直到碰到第一个字符串结束符\0为止然后返回计数器值sizeof是C语言的关键字它以字节的形式给出了其操作数的存储大小操作数可以是一个表达式或括在括号内的类型名操作数的存储大小由操作数的类型决定。
strlen() 函数计算的是 字符串的长度即从字符串的开头到 第一个空字符 (\0) 之前的字符数。在这种情况下字符串 \0 只有一个字符它就是 空字符 \0因此它的长度为 0。
strlen(\0); // 结果是 0因为字符串仅包含一个 \0 终止符sizeof() 计算的是 操作数的大小通常是以字节为单位。在 C 中字符串字面量 str 的实际类型是 字符数组并且这个数组总是包括一个额外的空字符 \0 作为结束符。因此字符串 \0 实际上是一个包含两个字符的字符数组\0 和 \0 终止符。所以 sizeof(\0) 结果是 2。
sizeof(\0); // 结果是 2因为字符串 \0 包含两个字符\0 和 \0 终止符10.sizeof和strlen有什么区别?
strlen与 sizeof的差别表现在以下5个方面, 1.sizeof是运算符(是不是被弄糊涂了?事实上sizeof既是关键字,也是运算符但不是函数)而strlen是函数。 sizeof后如果是类型则必须加括弧如果是变量名则可以不加括弧。
2. sizeof运算符的结果类型是 size_t它在头文件中 typedef 为 unsigned int类型。该类型保证能够容纳实现所建立的最大对象的字节大小
3. sizeof可以用类型作为参数strlen只能用char*作参数而且必须是以“0结尾的。 sizeof还可以以函数作为参数如intg()则 sizeof(g())的值等于 sizeof( int的值在32位计算机下该值为4。
4.大部分编译程序的 sizeof都是在编译的时候计算的,所以可以通过 sizeof(x)来定义数组维数。而 strlen则是在运行期计算的,用来计算字符串的实际长度不是类型占内存的大小。例如charstr[20]0123456789,字符数组str是编译期大小已经固定的数组在32位机器下为sizeof(char)*2020,而其 strlen大小则是在运行期确定的所以其值为字符串的实际长度10.当数组作为参数传给函数时传递的是指针而不是数组即传递的是数组的首地址。 11.不使用 sizeof如何求int占用的字节数?
#include stdio.h#define Mysizeof(value) ((char *)(value 1) - (char *)value)int main() {int i;double f;double *q;// 输出各个变量占用的字节数printf(%d\n, Mysizeof(i)); // 输出 int 类型的字节数printf(%d\n, Mysizeof(f)); // 输出 double 类型的字节数printf(%d\n, Mysizeof(q)); // 输出 double* 类型的字节数return 0;
}(char *)(value 1)将 value 的地址向后移动一个 value 类型的单位如 int移动 1 个 int 的大小。
(char *)value获取 value 的起始地址。
两者相减即可得到 value 类型的字节数因为指针的差值以 char 的大小为单位而 char 是 1 字节。
value 1 是类型安全的它表示从当前地址向后移动一个变量的单位。
将地址强制转换为 (char *)使得指针的差值以字节为单位。 12.C语言中 struct与 union的区别是什么?
比较项目struct结构体union联合体内存分配每个成员有独立的存储空间大小是所有成员大小的累加值考虑字节对齐。所有成员共用同一块内存大小等于最大成员的大小考虑字节对齐。成员访问所有成员可以独立访问且互不影响。同一时刻只能访问一个成员写入一个成员会覆盖其他成员的值。用途用于保存多个相关但独立的数据。用于在同一存储区域保存多个数据节省内存。字节对齐根据成员类型和字节对齐规则进行分配。最大成员决定内存分配并根据字节对齐规则调整大小。适用场景常用于多种类型数据的组合使用。常用于需要节省内存或多种数据类型共用时。
typedef union {double i; // 8 bytesint k[5]; // 5 × 4 bytes 20 byteschar c; // 1 byte
} DATE;typedef struct data {int cat; // 4 bytesDATE cow; // 24 bytes (union, 8-byte alignment)double dog; // 8 bytes
} too;DATE max;// sizeof(too) sizeof(max)DATE 的大小
联合体的大小由 最大成员大小 决定即 k[5]占用 20 字节。
为了满足 8 字节对齐需要调整到 8 的倍数实际占用 24 字节。 too 的大小
int cat: 4 字节。
DATE cow: 24 字节由前面计算。
double dog: 8 字节。
按 8 字节对齐too 总大小 4 4(填充) 24 8 40 字节。 max 的大小
与 DATE 相同占用 24 字节。 总大小
sizeof(too) sizeof(max) 40 24 64。 13.左值和右值是什么?
左值是指可以出现在等号左边的变量或表达式它最重要的特点就是可写(可寻址)。也就是说它的值可以被修改如果一个变量或表达式的值不能被修改那么它就不能作为左值。
int a 10; // a 是左值
a 20; // a 出现在赋值号的左边可修改其值右值是指只可以出现在等号右边的变量或表达式。它最重要的特点是可读。一般的使用场景都是把一个右值赋值给一个左值。
int b a 5; // (a 5) 是右值提供计算结果但无法修改通常左值可以作为右值但是右值不一定是左值。
类别左值L-value右值R-value定义表示内存中的一个地址可出现在赋值运算符左侧表示一个值不占据内存地址只能出现在赋值运算符右侧特点可寻址、可修改不可寻址、只提供值不能被修改作用提供一个持久的存储位置可读写提供数据通常用于计算或赋值示例变量int a; a 5;常量或表达式5; a 3;内存分配与具体内存地址绑定通常是临时值不绑定内存地址使用场景- 出现在赋值号左侧 - 可作为右值- 出现在赋值号右侧 - 参与计算互相关系左值可以用作右值右值不能用作左值函数返回值函数返回引用或指针是左值函数返回具体值是右值代码示例cbrint a 10; a 20;cbrint b a 5; 14.什么是短路求值?
短路求值Short-Circuit Evaluation是一种逻辑表达式的求值方式在逻辑运算 和 ||中一旦可以确定整个表达式的最终结果后续的部分就不会被执行。
逻辑或|| 如果左侧表达式为 true整个表达式为 true后续表达式不会执行。如果左侧表达式为 false需要计算右侧表达式。 逻辑与 如果左侧表达式为 false整个表达式为 false后续表达式不会执行。如果左侧表达式为 true需要计算右侧表达式。
#include stdio.h
int main() {int i 6; // i 6int j 1; // j 1if (i 0 || (j) 0); // 短路求值在此发生printf(%o\r\n, j); // 输出 j 的值return 0;
}条件判断i 0 || (j) 0
先计算 i 0结果为 true因为 i 6大于 0。
因为 || 运算中只需要一边为 true 就能确定整个表达式为 true因此不会执行右侧的 (j) 0。
j 不会被执行j 的值保持不变。
输出printf(%o\r\n, j);
j 仍然为 1因此输出 1八进制表示为 1。 15.a和a有什么区别?两者是如何实现的?
a前置自增先对变量自增 1再返回变量的值。
a后置自增先返回变量的值再对变量自增 1。
a 的实现过程
int a 5;
int temp a; // 保存当前值到临时变量 temp
a a 1; // 自增
return temp; // 返回保存的临时变量 tempa 的实现过程
int a 5;
a a 1; // 自增
return a; // 返回自增后的值