(嵌入式八股)No.1 C语言(2)
2.1 进制基础
常用进制表示
无论是使用哪种语言,使用哪种“进制”,描述的都是同一个“数值”。
int decimal = 100; // 十进制
int binary = 0b1100100; // 二进制(C99标准支持,前缀0b)
int octal = 0144; // 八进制(前缀0)
int hex = 0x64; // 十六进制(前缀0x)
#include <stdio.h>
int main() {
int num = 100;
printf("十进制: %d\n", num); // 100
printf("八进制: %o\n", num); // 144
printf("十六进制: %x\n", num); // 64(小写)
printf("十六进制: %X\n", num); // 64(大写)
printf("带前缀十六进制: %#x\n", num); // 0x64
return 0;
}
进制之间的转换
16进制里,每一位的权重,从右往左数,分别是:16^0, 16^1, 16^2, 16^3, 16^4, ……。
8进制里,每一位的权重,从右往左数,分别是:8^0, 8^1, 8^2, 8^3, 8^4, ……。
2进制里,每一位的权重,从右往左数,分别是:2^0, 2^1, 2^2, 2^3, 2^4, ……。
各进制对照表
2.2 命名规则与语法
标识符命名规则
- 允许字符:字母(A-Z, a-z)、数字(0-9)、下划线(_)
- 开头字符:必须是字母或下划线(不能是数字)
- 大小写敏感:count 与 Count 是不同的变量
- 长度限制:C89标准至少31个字符有效,C99至少63个字符
- 关键字不能作为标识符(如int, if, for等)
命名规范示例
// 良好命名示例 int student_count; // 小写+下划线(snake_case) float averageScore; // 驼峰命名法 #define MAX_SIZE 100 // 宏定义全大写 const float PI = 3.14; // 常量可首字母大写 // 非法命名示例 int 2nd_value; // ❌ 以数字开头 float total-score; // ❌ 包含减号 char int; // ❌ 使用关键字
常见命名约定
// 变量:描述性名词 int age, counter, buffer_size; // 函数:动词开头 void calculate_total(); int get_max_value(); // 指针:p开头或ptr结尾 int *p_data; int *data_ptr; // 布尔类型:is/has/can开头 int is_valid; int has_finished;
操作符优先级
2.3 库函数(如果你的技术栈是 c 语言的话,面试官可能会考察一些库函数的底层实现)
sizeof 和 strlen(这个在笔试里面会考)
sizeof 和 strlen 是两个非常常用的运算符和函数,它们在功能和用途上有明显的区别。
区别
示例
//普通变量
int a = 10;
printf("%zu\n", sizeof(a)); // 输出:4(假设 int 为4字节)
// strlen(a); ❌ 错误:a不是字符串
//字符串常量 vs 字符数组
char str1[] = "Hello";
char *str2 = "Hello";
printf("%zu\n", sizeof(str1)); // 6 (包括 '\0')
printf("%zu\n", sizeof(str2)); // 8 (指针本身的大小,在64位系统上)
printf("%zu\n", strlen(str1)); // 5 (不含 '\0')
printf("%zu\n", strlen(str2)); // 5
解释:
sizeof(str1):数组占 6 个字节('H','e','l','l','o','\0')
sizeof(str2):只返回指针大小(通常 8 字节)
strlen(str1) / strlen(str2):计算字符串内容长度(直到 \0)
//字符串中途有 \0
char msg[] = "Hi\0NEU";
printf("%zu\n", sizeof(msg)); // 7 (包含整个数组长度)
printf("%zu\n", strlen(msg)); // 2 (到第一个 '\0' 为止)
strlen() 会在遇到第一个 \0 时停止计数,而 sizeof 不会。
总结
- sizeof: 用于获取变量或数据类型的大小(以字节为单位)。在编译时计算,不会增加运行时的开销。适用于数组、结构体、变量等。
- strlen: 用于计算字符串的长度(以字符为单位)。在运行时计算,会逐个字符检查字符串,直到遇到终止符 \0。适用于字符串操作,但需要注意字符串的初始化和合法性。
malloc 和 calloc(这个也会考)
malloc 和 calloc 是两个用于动态内存分配的标准库函数。它们都属于 <stdlib.h> 头文件,用于在运行时分配内存。
二者对比
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
int *p1 = (int *)malloc(5 * sizeof(int));
int *p2 = (int *)calloc(5, sizeof(int));
for (int i = 0; i < 5; i++)
printf("malloc[%d] = %d, calloc[%d] = %d\n", i, p1[i], i, p2[i]);
free(p1);
free(p2);
return 0;
}
输出:
malloc[0] = -858993460, calloc[0] = 0
说明 malloc 分配的内存未初始化,而 calloc 分配的内存全是 0。
注意事项:
- 返回类型是void *,所以在 C 中不需要强制类型转换(但 C++ 需要)。
int *p = malloc(10 * sizeof(int)); // C语言推荐写法
- 失败返回 NULL,要检查分配结果:
if (p == NULL) { perror("malloc failed"); exit(1); }
- 必须使用 free() 释放,否则会造成内存泄漏。
- 使用sizeof确保类型安全:
int *p = malloc(5 * sizeof(*p)); // 推荐写法,更安全
- calloc清零不仅仅是数值型,结构体、数组、指针都会初始化为 0(指针为 NULL)。
- 不要混用分配和释放函数:
用 malloc/calloc/realloc → 用 free。
用 new → 用 delete。
延伸:malloc 底层原理(了解)(来源小林 coding)

strcpy 和 memcpy
strcpy用法
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Hello";
char dest[10];
strcpy(dest, src);
printf("dest = %s\n", dest); // 输出: Hello
return 0;
}
会自动复制 'H' 'e' 'l' 'l' 'o' '\0'。
目标空间必须足够大(至少比源字符串多 1 字节)。
memcpy 用法
#include <stdio.h>
#include <string.h>
int main() {
int src[3] = {1, 2, 3};
int dest[3];
memcpy(dest, src, sizeof(src));
for (int i = 0; i < 3; i++)
printf("%d ", dest[i]); // 输出: 1 2 3
return 0;
}
按字节拷贝,共 sizeof(src) 字节;
不需要也不会添加 \0。
场景 | 适合的函数 |
字符串复制(以 | ✅ |
任意内存块(包括二进制、结构体、数组) | ✅ |
可能存在内存重叠 | 🚫 不用 |
2.4 其余常考手撕实现:
你在手写代码时,请自查以下几点:
- 判空了没? (输入指针是否为 NULL) —— 不做必挂。
- const加了没? (源数据 src 应该是只读的) —— 加了加分。
- 返回值对不对? (strcpy 和 memcpy 需要返回 dest 以支持链式操作 len = strlen(strcpy(d, s)) ) —— 体现专业度。
- 指针类型转换? (memcpy 操作 void* 必须转为 char*) —— 不做编译报错。
- 结束符检查? (字符串循环条件是 \0)。
建议: 重点练习 memmove 和 strcpy还有memcpy,这是出现频率最高的“杀手锏”。
memcpy (内存拷贝)
考点:void* 指针的转换、按字节拷贝、返回 dest 指针以支持链式调用。
注意: 标准 memcpy不保证处理内存重叠,这是它和 memmove 的区别。
void *my_memcpy(void *dest, const void *src, size_t n) {
if (dest == NULL || src == NULL) {
return NULL;
}
char *p_dest = (char *)dest;
const char *p_src = (const char *)src;
// 必须转为 char* (按字节) 进行拷贝
while (n--) {
*p_dest++ = *p_src++;
}
return dest;
}
memmove (内存移动) —— 顶级考点
考点: 当 dest 和 src 的内存区域发生重叠时,如何安全拷贝?
- 情况 1 (dest < src):从前往后拷(同 memcpy)。
- 情况 2 (dest > src):从后往前拷(防止源数据被覆盖)。
void *my_memmove(void *dest, const void *src, size_t n) {
if (dest == NULL || src == NULL) {
return NULL;
}
char *p_dest = (char *)dest;
const char *p_src = (const char *)src;
// 判断是否重叠且 dest 在 src 后面
if (p_dest > p_src && p_dest < p_src + n) {
// 从后往前拷贝
p_dest += n - 1;
p_src += n - 1;
while (n--) {
*p_dest-- = *p_src--;
}
} else {
// 正常情况:从前往后拷贝
while (n--) {
*p_dest++ = *p_src++;
}
}
return dest;
}
strcpy (字符串拷贝)
考点:const 修饰源字符串、判断 \0 结束符、返回 dest。
陷阱: 不要忘了最后的 \0 是随循环赋值过去的,还是需要额外补。下面的写法最简洁。
char *my_strcpy(char *dest, const char *src) {
if (dest == NULL || src == NULL) {
return NULL;
}
char *ret = dest; // 保存首地址用于返回
// *dest++ = *src++ 先赋值,再判断值是否为 '\0'
while ((*dest++ = *src++) != '\0');
return ret;
}
strcmp (字符串比较)
考点: 必须使用 unsigned char 进行比较(标准规定),否则处理扩展 ASCII 码时可能出错。
int my_strcmp(const char *s1, const char *s2) {
assert(s1 != NULL && s2 != NULL); // 面试时最好写上断言或判空
while (*s1 && (*s1 == *s2)) {
s1++;
s2++;
}
// 转为无符号字符相减
return *(unsigned char *)s1 - *(unsigned char *)s2;
}
strlen (字符串长度)--中微半导体考了个这个。。。。
考点: 很简单,但要快。不要把 \0 算进去。
size_t my_strlen(const char *str) {
assert(str != NULL);
const char *p = str;
while (*p != '\0') {
p++;
}
return p - str; // 指针相减得到长度
}
atoi (字符串转整型)
考点: 空格处理、正负号处理、非数字字符处理。
进阶考点: 数据溢出处理(一般面试写出基础版即可,但要口头提到溢出问题)。
int my_atoi(const char *str) {
if (str == NULL) return 0;
int result = 0;
int sign = 1;
// 1. 跳过前导空格
while (*str == ' ') str++;
// 2. 处理正负号
if (*str == '-' || *str == '+') {
if (*str == '-') sign = -1;
str++;
}
// 3. 处理数字
while (*str >= '0' && *str <= '9') {
result = result * 10 + (*str - '0');
// 此处可以加入溢出判断逻辑
// if (result > INT_MAX ...)
str++;
}
return sign * result;
}
#嵌入式软件八股##牛客在线求职答疑中心##开工第一帖#从入门到上岸,一站式搞定求职! 本硕纯机械,无竞赛无论文,后转行嵌入式软件开发(因为课题组师哥转嵌入式拿到30Woffer之后狠狠心动),秋招最终收获35W+offer可以为27届或者28届的的UU们提供参考,可以关注一下!!!
