白乐天

道阻且长,行则将至。

C语言

C语言基础编程

Hello World

1
2
3
4
5
6
#include<stdio.h>

int main(){
printf("Hello World!");
return 0;
}

宏定义

宏定义是 C/C++ 语言中的一种预处理指令,用于在编译之前对代码进行文本替换。通过宏定义,可以定义常量、代码片段或者参数化的宏,以提高代码的可读性、可维护性以及复用性。

宏定义由 #define 指令实现,属于预处理阶段的内容。

1
#define 宏名 替换文本

示例

1
2
#define PI 3.14159
#define MAX_SIZE 100

基本数据类型

char

长度:1字节

  • unsigned char

  • short

    2字节

  • unsigned short

  • int

    4字节

  • unsigned int

  • long

  • unsigned long

  • long long

    8字节

  • unsigned long long

  • float

    4字节

    float 类型的变量需要在值后加 fF 后缀,例如:3.14f

  • double

    8字节

  • void

运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
算术运算符
+
-
*
/
%
++
--
比较运算符
==
!=
>
<
>=
<=
逻辑运算符
&&
||
!
位运算符
&
|
^
~
<<
>>
赋值运算符
=
+=
-=
*=
/=
%=
<<=
>>=
&=
^=
|=
其他运算符
sizeof() // 返回变量或数据类型的大小
& // 取地址
* // 解引用
?: // 三元运算符

流程控制

if

1
2
3
if(expression){
...;
}
1
2
3
4
5
  if(expression){
...;
}else{
...;
}
1
2
3
4
5
6
7
  if(expression){
...;
}else if(expression){
...;
} else{
...;
}

switch

1
2
3
4
5
6
7
8
9
10
11
12
switch (表达式) {
case 常量表达式1:
// 语句块1
break;
case 常量表达式2:
// 语句块2
break;
...
default:
// 默认语句块
break;
}

while

1
2
3
while (条件表达式) {
// 循环体
}

do-while

1
2
3
do {
// 循环体
} while (条件表达式);

for

1
2
3
for (初始化语句; 条件表达式; 更新语句) {
// 循环体
}

continue

continue 用于跳过当前循环的剩余部分,直接进入下一次迭代。

break

用于跳出最近的循环或 switch 语句。

goto

数组

一维数组的定义方式

1
2
3
4
5
int arr[10];

int arr[10] = {1,2,3,4,5,6};

int arr[] = {1,2,3,4,5,6};

函数

1
2
3
4
return_type function_name( parameter list )
{
body of the function
}

指针

结构体

结构体(struct)是一种用户自定义的数据类型,可以将不同类型的数据组合在一起,形成一个更复杂的数据结构。

结构体的定义与使用

访问结构体成员

通过点操作符 (.) 来访问结构体的成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>

struct Person {
char name[50];
int age;
float height;
};

int main() {
struct Person person1;

// 给成员赋值
strcpy(person1.name, "Alice");
person1.age = 30;
person1.height = 5.5;

// 打印成员
printf("Name: %s\n", person1.name);
printf("Age: %d\n", person1.age);
printf("Height: %.2f\n", person1.height);

return 0;
}

结构体指针

通过指针来访问结构体成员,使用箭头操作符 (->) 来代替点操作符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>

struct Person {
char name[50];
int age;
float height;
};

int main() {
struct Person person1;
struct Person *ptr = &person1;

strcpy(ptr->name, "Bob");
ptr->age = 25;
ptr->height = 5.9;

printf("Name: %s\n", ptr->name);
printf("Age: %d\n", ptr->age);
printf("Height: %.2f\n", ptr->height);

return 0;
}

文件读写

C 语言的文件读写操作主要依赖 标准 I/O 库stdio.h),通过 FILE * 指针来管理文件。

fopen

1
2
3
4
FILE *fopen(const char *filename, const char *mode);

// filename:文件路径
// mode:文件模式(如 r 读取,w 写入等)

mode

模式 描述
r 打开一个已有的文本文件,允许读取文件。
w 打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。如果文件存在,则该会被截断为零长度,重新写入,会覆盖原来的内容。
a 打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。
r+ 打开一个文本文件,允许读写文件。
w+ 打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件。
a+ 打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。

write

fwrite()

将数据写入二进制文件

1
2
3
4
5
6
7
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);

// ptr:指向要写入的数据的指针,通常是一个变量或者数组的地址。
// size:每个数据项的字节数,通常是单个数据类型的大小。例如,sizeof(int)、sizeof(char)、sizeof(struct) 等。
// count:要写入的元素个数。即 ptr 指向的数据的元素个数。
// stream:文件指针,指向已经打开的文件。
// return:返回成功写入的元素数量(即 count),如果出现错误或文件未打开,会返回 0。

fputc()

向文件写入一个字符。

1
2
3
4
5
int fputc(int ch, FILE *stream);

// ch:要写入的字符(作为 int 传递)。
// stream:指向已打开的文件的指针。
// return:成功时返回写入的字符,失败时返回 EOF(-1)。

fputs()

将字符串写入文件。

1
2
3
4
5
int fputs(const char *str, FILE *stream);

// str:要写入的字符串(以 \0 结尾)。
// stream:指向已打开的文件的指针。
// return:成功时返回 非负整数(通常为 0),失败时返回 EOF(通常为 -1)

fprintf()

将格式化数据写入文件。

1
2
3
4
5
6
int fprintf(FILE *stream, const char *format, ...);

// stream:指向已打开的文件的指针。
// format:格式化字符串,与 printf() 用法相同。
// ...:要写入的变量数据。
// 成功时返回写入的字符数,失败时返回负值。

read

fread()

从二进制文件读取数据

1
2
3
4
5
6
7
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);

// ptr:指向存储读取数据的内存区域的指针。
// size:每个数据项的字节数,通常是单个数据类型的大小。
// count:要读取的元素个数。
// stream:文件指针,指向已经打开的文件。
// return:返回成功读取的元素个数。如果发生错误或到达文件末尾(EOF),则返回值小于请求的 count。

fgetc()

从文件读取一个字符。

1
2
3
4
int fgetc(FILE *stream);

// stream:指向已打开的文件的指针。
// return:成功时返回读取的字符(作为 int 返回),失败时返回 EOF(通常为 -1)。

fgets()

从文件读取一行字符串。

1
2
3
4
5
6
char *fgets(char *str, int n, FILE *stream);

// str:用于存储读取字符串的字符数组(缓冲区)。
// n:最多读取 n-1 个字符,遇到 换行符 (\n) 或文件结束 (EOF) 停止,最后自动添加 \0 结束符。
// stream:指向已打开的文件的指针。
// return:成功时返回 str(字符串的指针),失败或到达 EOF 时返回 NULL。

fscanf()

从文件中读取格式化数据。

1
2
3
4
5
6
int fscanf(FILE *stream, const char *format, ...);

// stream:指向已打开的文件的指针。
// format:格式化字符串,与 scanf() 用法相同。
// ...:指向存储读取数据的变量指针。
// return:成功读取的变量个数,读取失败(如文件结束 EOF)时返回 负值。

文件指针操作

ftell()

获取文件指针当前位置。

1
2
3
4
long ftell(FILE *stream);

// stream:指向已打开文件的指针。
// 成功:返回当前文件指针的位置(从文件开头算起的字节数),失败:返回 -1L,需使用 perror() 或 ferror() 检查错误。

fseek()

移动文件指针。

1
2
3
4
5
6
7
8
9
int fseek(FILE *stream, long offset, int origin);

// stream:指向已打开文件的指针。
// offset:偏移量(以字节为单位)。
// origin:
// SEEK_SET:文件开头 + offset(从文件头部定位)。
// SEEK_CUR:当前文件指针位置 + offset(从当前位置定位)。
// SEEK_END:文件末尾 + offset(从文件尾部定位,通常 offset 为负数)。
// return:成功:返回 0,失败:返回 -1,可以用 perror() 检查错误。

rewind()

重置文件指针。

1
void rewind(FILE *stream);

将文件指针重置到文件开头(相当于 fseek(fp, 0, SEEK_SET);)。

不会返回错误,也不会返回值。

内存分区

代码区

存放程序的机器指令,也就是程序的可执行代码。

静态区(全局区)

用于全局变量、静态变量和常量(全局常量、字符串常量)的存储。这部分内存的生命周期从程序开始直到程序结束。

栈(stack)

用于存储局部变量和函数调用时的上下文(如返回地址与参数等)。

堆(heap)

动态分配的内存区域,用于存放程序运行时动态创建的数据。

stdio.h

占位符

1
2
3
4
5
6
7
%d 十进制有符号整数
%u 十进制无符号整数
%f 浮点数
%c 单个字符
%s 字符串
%x 十六进制无符号整数
%X 大写十六进制无符号整数

库变量

size_t

无符号整数类型,它是 sizeof 关键字的结果。

1
unsigned int

FILE

文件流类型,适合存储文件流信息的对象类型。

fpos_t

文件位置类型,适合存储文件中任何位置的对象类型。

库函数

printf()

1
2
3
4
5
int printf(const char *format, ...);

// format:格式化字符串,用于指定输出的内容和格式。
// ...:可变参数,表示要输出的数据,可以是变量或值。
// 返回值:返回成功打印的字符数量(不包括格式字符串中的额外字符,如 \n、% 等)。如果发生错误,通常返回 -1。

sprintf()

用于将格式化的数据写入字符串。

1
2
3
4
5
6
int sprintf(char *str, const char *format, ...);

// str: 一个字符数组,用于存储格式化后的输出结果。
// format: 一个格式字符串,指定输出的格式。
// ...: 根据格式字符串提供的其他参数,用于格式化输出。
// 返回值:返回写入 str 中的字符数,不包括结尾的 \0 字符。如果发生错误,返回一个负值。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>

int main() {
char buffer[100];
int n = 42;
float pi = 3.14159;

// 使用 sprintf 格式化数据并写入 buffer
int written = sprintf(buffer, "The answer is %d and pi is approximately %.2f.", n, pi);

// 打印结果
printf("Formatted string: %s\n", buffer);
printf("Number of characters written: %d\n", written);

return 0;
}

>>>
Formatted string: The answer is 42 and pi is approximately 3.14.
Number of characters written: 46

snprintf()

用于将格式化的数据写入字符串。

1
2
3
4
5
6
7
int snprintf(char *str, size_t size, const char *format, ...);

// str: 一个字符数组,用于存储格式化后的输出。
// size: 字符数组的大小,即 str 能容纳的最大字符数(包括终止的 \0 字符)。
// format: 一个格式字符串,指定输出的格式。
// ...: 依据格式字符串提供的其他参数。
// 返回值:返回写入 str 中的字符数,不包括结尾的 \0 字符。如果返回值大于或等于 size,说明输出被截断了。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>

int main() {
char buffer[50];
int n = 42;
float pi = 3.14159;

// 使用 snprintf 格式化数据并写入 buffer
int written = snprintf(buffer, sizeof(buffer), "The answer is %d and pi is approximately %.2f.", n, pi);

// 打印结果
printf("Formatted string: %s\n", buffer);
printf("Number of characters written: %d\n", written);

return 0;
}

>>>
Formatted string: The answer is 42 and pi is approximately 3.14.
Number of characters written: 46

stdlib.h

库函数

calloc()

用于分配一块连续内存,并初始化为零。

1
2
3
4
5
void* calloc(size_t num, size_t size);

// num:要分配的元素数量。
// size:每个元素的大小(字节数)。
// 返回值:返回一个 void* 类型的指针,指向分配的内存块。如果分配失败,返回 NULL。

malloc()

用于动态分配指定字节数的内存空间,并返回指向这块内存的指针。内存内容未初始化,可能包含任意值(垃圾数据)。

1
2
3
4
void* malloc(size_t size);

// size:要分配的内存块的字节数。
// 返回值:返回一个 void* 类型的指针,指向分配的内存块。如果分配失败,返回 NULL。

realloc()

用于重新调整已经分配内存的大小。它可以增加或减少已分配内存的空间。

1
2
3
4
5
void* realloc(void* ptr, size_t size);

// ptr:指向已分配内存的指针。可以是 malloc 或 calloc 返回的指针。
// size:新的内存块大小(字节数)。
// 返回值:返回一个指向新分配内存的指针。

free()

用于释放由 malloccallocrealloc 分配的动态内存,避免内存泄漏。

1
2
3
void free(void* ptr);

// ptr:指向要释放内存块的指针。

string.h

strlen()

用于计算字符串的长度。

1
2
3
4
size_t strlen(const char *str);

// str: 一个指向以 null 结尾的字符串的指针。
// 返回值:返回 str 字符串的长度,不包括末尾的 null 字符('\0')。

memset()

用于将一块内存区域的内容设置为指定的值。

1
2
3
4
5
6
void* memset(void* ptr, int value, size_t num);

// ptr: 指向要设置的内存块的指针。
// value: 要设置的值(以 int 类型传入,但实际上会转为 unsigned char 类型赋值)。
// num: 要设置的字节数。
// 返回值:memset 返回指向 ptr 的指针,通常用于链式调用。

memcmp()

1
2
3
4
5
int memcmp(const void *s1, const void *s2, size_t n);

// s1:指向第一个内存区域的指针。
// s2:指向第二个内存区域的指针。
// n:要比较的字节数。

如果返回 0,则表示前 n 个字节内的内容相同。

如果返回非 0 值,则表示存在不同的字节。其具体含义为:

  • 返回一个 负值,表示在第一个不同的字节处,s1 的值小于 s2 中对应字节的值。
  • 返回一个 正值,表示在第一个不同的字节处,s1 的值大于 s2 中对应字节的值。

比较时,memcmp 会将每个字节当作 unsigned char 来处理,这样可以避免因为符号位的不同而导致的比较问题。

memcpy()

通过逐字节复制的方式将一块内存数据从源地址拷贝到目标地址

1
2
3
4
5
void *memcpy(void *dest, const void *src, size_t n);

// dest:目标内存地址(复制后的存储位置)。
// src:源内存地址(要复制的数据来源)。
// n:要复制的字节数。

返回目标地址 dest 的指针。

strdup()

动态分配内存,并将字符串 s 的内容复制到新分配的内存中。

1
2
3
char *strdup(const char *s);

// s: 是要复制的源字符串(以 \0 结尾)。

time.h

库变量

size_t

无符号整型,sizeof运算符的结果。

1
unsigned int

clock_t

表示处理器的时钟周期数。

1
2
typedef long clock_t; // 或者
typedef int clock_t;

time_t

用于存储时间戳

1
2
typedef long time_t; //或者
typedef int time_t;

struct tm

tm 是一个结构体类型,用于表示具体的时间信息,通常是通过将时间戳(time_t)转换成更易于理解和操作的日期和时间格式。

1
2
3
4
5
6
7
8
9
10
11
struct tm {
int tm_sec; // 秒 (0-59)
int tm_min; // 分 (0-59)
int tm_hour; // 小时 (0-23)
int tm_mday; // 一个月中的日期 (1-31)
int tm_mon; // 月份 (0-11) -> 0 表示 1 月,11 表示 12 月
int tm_year; // 从 1900 年开始的年数 (例如,2024 年就是 124)
int tm_wday; // 星期几 (0-6),0 表示星期天
int tm_yday; // 一年中的第几天 (0-365),0 表示 1 月 1 日
int tm_isdst; // 夏令时标志,正值表示夏令时,0 表示非夏令时,负值表示信息不可用
};

库函数

time()

time()函数用于获取当前时间戳。

1
time_t time(time_t *timer);
  • 如果参数为NULL或0,返回当前时间的time_t值。
  • 如果传递一个有效指针,函数会将当前时间保存到指针所指向的变量中,并返回相同的值。

localtime()

用于将 time_t 类型的时间值转换为结构化的本地时间格式(struct tm)。

1
struct tm* localtime(const time_t* timer);

将 time_t 类型的时间转换为本地时间,返回一个 struct tm 结构,表示本地时间的各个组成部分(年、月、日、小时、分钟、秒等)。

localtime_r()

用于将 time_t 类型的时间值转换为结构化的本地时间格式(struct tm)。
由于它将结果存储在用户提供的缓冲区中,多个线程可以同时调用 localtime_r 而不会发生冲突,因此它是线程安全的。

1
struct tm* localtime_r(const time_t* timer, struct tm* buf);

localtime_s()

localtime_s 是 C11 标准引入的函数,功能和 localtime_r 类似。它将 time_t 转换为本地时间,并将结果存储在用户提供的缓冲区中。

1
struct tm* localtime_s(const time_t* restrict timer, struct tm* restrict buf);

localtime_s 在 C11 标准中引入时,具有更严格的错误检查。如果出现错误(例如无效的时间值),通常会返回一个错误码,而不是直接返回 NULL。这增加了函数的安全性和可靠性。

pthread.h

库函数

pthread_create()

用于创建一个新的线程。

1
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
  • pthread_t *thread:
    指向 pthread_t 类型的指针,用于存储新创建线程的线程 ID。
  • const pthread_attr_t *attr:
    指向线程属性对象的指针,可以是 NULL,表示使用默认属性。
  • void *(*start_routine)(void *):
    线程的起始函数,这是一个函数指针,它指向一个函数,该函数接收一个 void * 类型的参数,并返回一个 void * 类型的值。
  • void *arg:
    传递给起始函数的参数。
  • 返回值
    成功返回0,否则返回错误码。

sched.h

clone

1
2
3
4
5
6
7
// 系统调用版本(低级别)
long clone(unsigned long flags, void *stack, int *parent_tid, int *child_tid,
unsigned long tls);

// glibc包装版本(高级别)
int clone(int (*fn)(void *), void *stack, int flags, void *arg, ...
/* pid_t *parent_tid, struct user_desc *tls, pid_t *child_tid */ );

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <errno.h>

// 定义栈大小
#define STACK_SIZE (1024 * 1024) // 1MB

// 子线程函数
static int childFunc(void *arg)
{
printf("Child thread is running\n");
return 0;
}

int main()
{
// 分配栈空间
void *stack = malloc(STACK_SIZE);
if (!stack) {
perror("malloc failed");
exit(1);
}

// 调用clone
int flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND |
CLONE_THREAD | CLONE_SYSVSEM;

pid_t pid = clone(childFunc,
(char *)stack + STACK_SIZE, // 栈顶
flags,
NULL); // 参数

if (pid == -1) {
perror("clone failed");
free(stack);
exit(1);
}

printf("clone() returned %d\n", pid);
sleep(1); // 给子线程一些运行时间

free(stack);
return 0;
}

sys/ptrace.h

ptrace()

ptrace 是 Unix/Linux 系统中用于进程跟踪(tracing)和调试的重要系统调用,它允许一个进程(通常是调试器)控制和监视另一个进程(通常是被调试的子进程)的执行情况,包括读取和修改目标进程的内存、寄存器等信息。

1
2
3
4
5
6
7
#include <sys/ptrace.h>       
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

// request:要执行的操作类型;
// pid:被追踪的目标进程ID;
// addr:被监控的目标内存地址;
// data:保存读取出或者要写入的数据。