…
参考文档
环境配置
需要GCC,可以使用MinGW-w64,并配置/bin
目录到环境变量。
编译文件使用命令:
1 2
| gcc main.c gcc main.c -o main.out
|
注意:在VC与GCC上,对于long
型有关的变量,其长度定义的不一致。
版本历史
C语言有:C89(90),C99,C11。
C99
新增关键字:_Bool
, _Complex
, _Imaginary
, inline
, restrict
复合字面量:初始化结构的时候允许对特定的元素赋值,struct test{int a, b, c, d;} foo = { .a = 1, .c = 3, 4, .b = 5 };
。
格式化字符串中,利用 \u 支持 unicode 的字符。
修改了 /% 处理负数时的定义,这样可以给出明确的结果,例如在C89中-22 / 7 = -3, -22% 7 = -1,也可以-22 / 7= -4, -22% 7 = 6。 而C99中明确为 -22 / 7 = -3, -22% 7 = -1,只有一种结果。
C11
新增关键字:_Alignas
, _Alignof
, _Atomic
, _Generic
, _Noreturn
, _Static_assert
, _Thread_local
。
alignof(T)
返回T的对齐方式,aligned_alloc()
以指定字节和对齐方式分配内存,头文件<stdalign.h>
定义了这些内容。
fopen()
增加了新的创建、打开模式x
,在文件锁中比较常用。
quick_exit()
,又一种终止程序的方式,当exit()
失败时用以终止程序。
多线程:头文件<threads.h>
定义了创建和管理线程的函数,新的存储类修饰符_Thread_local
限定了变量不能在多线程之间共享。
_Atomic
类型修饰符和头文件<stdatomic.h>
。
改进的Unicode支持和头文件<uchar.h>
。
常量
前缀:0x
表示十六进制,0
表示八进制,默认是十进制。
后缀:u
表示无符号,l
表示长整数。
size_t:64位中表示long long unsigned int
,非64位中为long unsigned int
。size_t大于等于地址线宽度。是sizeof的返回类型,就是表示一个size。
ptrdiff_t:用来保存两个指针减法操作的结果。
字符串常用函数
1 2 3 4 5 6
| strcpy(s1, s2); strcat(s1, s2); strlen(s); strcmp(s1, s2); strchr(s, ch); strstr(s1, s2);
|
结构体
结构体在函数传参中可以值传递,也可以地址传递。值传递时,不会改变原有的结构体,也就是调用时,结构体被复制了一份。
位域
1 2 3 4 5 6
| struct bs{ unsigned a:4; unsigned :4; unsigned b:4; unsigned c:4 }
|
标准输入输出
1 2 3
| int scanf(const char *format, ...); int printf(const char *format, ...);
|
占位修饰符:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| "%+" 显示正负 "%-" 左对齐 "% " 显示负或空 "%#" 显示进制格式,科学计数格式 "%0" 0填充前导数值 "%4" "%*" 字宽最小值,*表示从参数中获取 "%5.2f" 保留几位小数和整数 "%6.4hd" short int / unsigned short int, hhd 表示 char/uchar "%j" int uint "%l" long "%ll" long long "%L" long double "%t" 表示 ptrdiff_t "%z" 表示 size_t
|
文件读写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| FILE *fopen( const char * filename, const char * mode ); int fclose( FILE *fp );
int fgetc( FILE * fp );
char *fgets( char *buf, int n, FILE *fp ); int fscanf(FILE *fp, const char *format, ...); int fputc( int c, FILE *fp ); int fputs( const char *s, FILE *fp ); int fprintf(FILE *fp,const char *format, ...);
size_t fread(void *ptr, size_t size_of_elements, size_t number_of_elements, FILE *a_file); size_t fwrite(const void *ptr, size_t size_of_elements, size_t number_of_elements, FILE *a_file);
int feof ( FILE * fp ); int ferror ( FILE *fp ); void rewind ( FILE *fp ); int fseek ( FILE *fp, long offset, int origin ); int ftell ( FILE * fp );
|
FILE结构体为
1 2 3 4 5 6 7 8
| typedef struct _iobuf { int cnt; char *ptr; char *base; int flag; int fd; } FILE;
|
预定义宏
__DATE__
当前日期,一个以 “MMM DD YYYY” 格式表示的字符常量。
__TIME__
当前时间,一个以 “HH:MM:SS” 格式表示的字符常量。
__FILE__
这会包含当前文件名,一个字符串常量。
__LINE__
这会包含当前行号,一个十进制常量。
__STDC__
当编译器以 ANSI 标准编译时,则定义为 1。
字符串常量化运算符(#):转化常量为字符串
标记粘贴运算符(##):拼接合并变量
#pragma:使用标准化方法,向编译器发布特殊的命令到编译器中
错误处理
1 2 3 4 5 6 7 8 9 10 11 12
| #include <errno.h> extern int errno; perror("An error occered") strerror(errno) stderr
fprintf(stderr, "错误号: %d\n", errno); perror("通过 perror 输出错误"); fprintf(stderr, "打开文件错误: %s\n", strerror(errno));
exit(EXIT_SUCCESS); exit(EXIT_FAILURE);
|
可变参数
1 2 3 4 5 6 7 8 9 10 11 12
| #include <stdarg.h> int func(int, ... ){ va_list valist; va_start(valist, num); for (i = 0; i < num; i++){ sum += va_arg(valist, int); } va_end(valist); }
|
内存管理
1 2 3 4
| void *calloc(int num, int size); void free(void *address); void *malloc(int num); void *realloc(void *address, int newsize);
|
命令行参数
1 2
| int main( int argc, char *argv[] )
|
Linux 下我们可使用 getopt 和 getopt_long 来对命令行参数进行解析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| int main(int argc, char *argv[]){ char *optstr = "p:n:m:c:"; struct option opts[] = { {"path", 1, NULL, 'p'}, {"name", 1, NULL, 'n'}, {"mtime", 1, NULL, 'm'}, {"ctime", 1, NULL, 'c'}, {0, 0, 0, 0}, }; int opt; while((opt = getopt_long(argc, argv, optstr, opts, NULL)) != -1){ switch(opt) { case 'p': } } findInDir(path); return 0; }
|
虚内存模型
Linux:
由低地址到高地址分别为:保留区,代码区,常量区,全局变量区,堆(以及未分配内存),动态链接库,(未分配内存以及)栈(向低地址增长)内核空间。
Windows
多线程
参考文档
在头文件 threads.h
中,定义和声明了支持多线程的宏、类型和函数。所有直接与线程相关的标识符,均以前缀 thrd_
作为开头。
线程操作:
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
|
int thrd_create(thrd_t *thr, thrd_start_t func, void *arg);
int thrd_join(thrd_t thr, int *result);
int thrd_detach(thrd_t thr);
thrd_t thrd_current(void);
int thrd_equal(thrd_t thr0, thrd_t thr1);
int thrd_sleep(const struct timespec*duration, struct timespec*remaining);
void thrd_yield(void);
_Noreturn void thrd_exit(int result);
struct timespec{ time_t tv_sec; long tv_nsec; }
|
当每个线程为各自的变量使用全局标识符时,为保留这些变量各自的数据,可以采用线程对象和线程存储。
线程对象(全局或静态对象):每一个线程拥有属于自己的线程对象实例(不共享)
1
| thread_local int var = 10;
|
线程存储:线程内部使用。
通过初始创建一个全局的键(key)表示一个指向线程存储的指针。
1 2 3 4 5 6 7 8 9 10 11 12
|
int tss_create(tss_t *key, tss_dtor_t dtor);
void tss_delete(tss_t key);
int tss_set(tss_t key, void*val);
void* tss_get(tss_t key);
|
线程间通信:使用条件变量,以等待来自另一个线程的通知。互斥要自行实现。
1 2 3 4 5 6 7 8 9 10 11 12
| int cnd_init(cnd_t *cond);
void cnd_destroy(cnd_t *cond);
int cnd_signal(cnd_t *cond);
int cnd_broadcast(cnd_t *cond);
int cnd_wait(cnd_t *cond, mtx_t *mtx);
int cnd_timedwait(cnd_t *restrict cond,mtx_t *restrict mtx,const struct timespec *restrict ts);
|
互斥操作:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
int mtx_init(mtx_t *mtx, int mutextype);
void mtx_destroy(mtx_t *mtx);
int mtx_lock(mtx_t *mtx);
int mtx_unlock(mtx_t *mtx);
|
原子对象与原子操作:stdatomic.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
_Atomic long counter = ATOMIC_VAR_INIT(0L); _Atomic long counter = ATOMIC_INIT(0L);
atomic_store(); atomic_exchange(); atomic_compare_exchange_strong();
atomic_flag_test_and_set(); atomic_flag_clear();
_Bool atomic_is_lock_free(const volatile A *obj);
|
内存次序:使用原子对象可以默认地防止此类重新排序。但是在较低的内存次序请求下,通过明确地使用原子操作提高性能。
每个原子操作的函数都有一个以 _explicit
结尾版本。如atomic_store_explicit()
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| enum memory_order { memory_order_relaxed, memory_order_release, memory_order_acquire, memory_order_consume, memory_order_acq_rel, memory_order_seq_cst, }
atomic_store_explicit(&aptr, (intptr_t)&data,memory_order_release); dp = (struct Data*)atomic_load_explicit(&aptr, memory_order_acquire);
|
内存屏障(栅栏):对于一个原子操作的内存次序请求,也可以通过一个原子操作单独指定。栅栏允许更大程度的内存顺序优化。
若参数值为 memory_order_release,函数 atomic_thread_fence()建立一个释放栅栏(releas fence)。在这种情况下,原子写操作必须在释放栅栏之后发生。
若参数值为 memory_order_acquire 或 memory_order_consume,函数 atomic_thread_fence()建立一个捕获栅栏(acquire fence)。在这种情况下,原子读操作必须在捕获栅栏之前发生。
1
| void atomic_thread_fence(memory_order order);
|
算法
1 2 3 4 5 6 7 8 9
| qsort(arr.data(), ARRAY_SIZE,sizeof(int), comp_function [a>b +, a<b -, a=b 0])
void *p = bsearch(&target, arr.data(), ARRAY_SIZE, sizeof(int), comp_function);
clock_t clk = clock();
snprintf(buf, 10, "%d", x);
|
参考库
assert.h: assert(ignore) ((void)0)
宏定义
ctype.h: 测试数据类型
errno.h: C 标准库中的特定函数修改它的值为一些非零值以表示某些类型的错误
float.h: 包含了一组与浮点值相关的依赖于平台的常量
limits.h: 决定了各种变量类型的各种属性
locale.h: 定义了特定地域的设置,比如日期格式和货币符号
math.h: 各种数学函数
setjmp.h: 定义了宏 setjmp()、函数 longjmp() 和变量类型 jmp_buf,该变量类型会绕过正常的函数调用和返回规则
signal.h: 定义了一个变量类型 sig_atomic_t、两个函数调用和一些宏来处理程序执行期间报告的不同信号
stdarg.h: 定义了一个变量类型 va_list 和三个宏,这三个宏可用于在参数个数可变时获取函数中的参数
stddef.h: 定义了各种变量类型和宏ptrdiff_t, size_t, wchar_t, NULL, offsetof
stdlib.h: size_t wchar_t NULL atof atoi atol strtod strtol等, malloc free, abort, exit, qsort, bsearch, system, abs, rand …
string.h: memcpy memmove memcmp memchr 以及字符串相关
time.h: struct tm, asctime, time, …