C初级知识(二)

/*  改变计算机存储单元里的数据或者输入输出操作都算Side Effect

Implementation - defined:
C标准没有明确定义,但要求编译器必须对此作出明确规定
unspecified:编译器自己可以决定,不必写在编译器文档中。
undefined:完全不缺定。C标准和编译器可能都没又规定怎么处理,甚至没有作出错误检查。例如数组访问越界。

Usual Arithmetic Conversion
如果一边是无符号数,另一边是有符号数。如果无符号数的Rank不低于有符号数,则把有符号数转换成另一边的无符号类型。
如果无符号数的Rank低于有符号数的Rank。如果有符号数类型能够覆盖这个无符号数类型的取值范围,则将无符号数类型转换成另一边的有符号类型。否则,也就是有符号数类型不足以覆盖这个无符号数类型,则将两边都转换成有符号数的Rank对应的无符号数类型。例如32位平台上的unsigned int和long做算数运算,将两边都转换成unsigned long。

如果一个表达式隐含着多个Side Effect,发生顺序未确定。
Sequence Point:
, && || :
? 的第一个操作数求值之后是Sequence Point。
在一个完整的声明末尾是Sequence Point。int a[10], b[20];
a[10]的末尾是,然后是b[20]。

运算优先级:
1,标识符,常量,()
2,后缀运算符,包括[],函数调用(),结构体取成员.,->, 后缀++, 后缀--, (从左到右依次计算) a.name++,先求a.name
3,单目运算符,包括前缀++,前缀--,sizeof,类型转换(),取址&,指针取值*,按位取反~,逻辑非!。如果一个操作数有多个前缀,则从右到左依次计算。例如~!a,先计算!a。
4, + - * / % 从左到右。
5,移位运算符 << , >>
6,关系运算符 > < <= >=
7,相等运算符 == , !=
8,按位与 &
9,按位异或 ^
10,按位或 |
11,逻辑与 &&
12,逻辑或 ||
13,条件运算符
14,条件 : ?
15,赋值 = 和各种复合赋值 *= , -= ,  /= , |= 。在双目运算符中只有这种是右结合的。
16,逗号运算符。
*/

#include 	

const int A = 10;                      /* .rodata段,只读变量 */
int a = 20;                            /* data段,global符号 */
static int b = 30;                     /* data段,local符号 */

/* 静态变量未初始化,则放在.bss段,初始化为0。 static 变量未初始化,也放在.bss段
.bss段在文件中不占用存储空间,在加载时这个段用0填充*/
int c;

int main ( int argc, char *argv[] )
{
        static int a = 40;                      /* data段 */
        char b[] = "hello world";
        register int c = 50;

        printf("Hello World %d\n", c); /* .rodata段,只读变量 字符串字面值 */

        return 0;
}

/*
 * 作用域(Scope):
 * 1,函数作用域(Function Scope), 只有语句标号属于函数作用域
 * 2,文件作用域(File Scope),全局变量,main,全局中的static
 * 3,函数原型作用域(Function Prototype Scope)
 */

/*
 * 标识符链接属性(Linkage)
 * 外部链接(External Linkage)
 * 内部链接(Internal Linkage) 全局变量,全局中的static变量
 * 无链接(No Linkage)
 * Previous Linkage  extern
 */

/*
 * 链接库
 *
 *
 * 打包成静态库
 *  ar rs libxxx.a aaa.o bbb.o ccc.o
 * 以上命令等价于ar r lib.............. && ranlib libxxx.a
 *
 * gcc main.c -L. -lstatck -Istack -o main
 * -L.告诉编译器在当前目录找库文件
 * -lstatck告诉编辑器要链接libstack库,-I选项告诉编译器去哪个目录找头文件
 *  -static告诉编译器只链接静态库。链接器在链接静态库时,会把静态库中的目标文件取出来和可执行文件真正链接在一起。链接器可以从静态库中只取出需要的部份来做链接。
 *
 *
 * 共享库在编译时要加-fPIC选项,PIC表示生成位置无关代码(Position Independent Code)。
 *
 * 程序运行时,要注意共享库位置。
 * 共享库搜索路径顺序:
 * 1,环境变量 LD_LIBRARY_PATH
 * 2,缓存文件/etc/ld.so.cache。这个缓存文件是ldconfig读取配置文件/etc/ld.so.conf之后生成的
 * 3,默认的系统路径 /usr/lib /lib
 *
 *
 *
 * 共享库的命名惯例:real name, soname 和 linker name
 * real name包含完整的共享库版本。例如 libcap.so.1.10
 * soname是一个符号链接的名字,只包含共享库的主版本号。主版本号一致即可保证接口一致。例如libcap.so.1
 * linker name只在编译链接时使用。有的link name是一个符号链接的名字,有的是一个符号链接
 *
 *  gcc -shared -WL,-soname,libstatck.so.1 -o libstack.so.1.0 aaa.o bbb.o
 *  -WL之后是GCC传递给链接器的选项。本例中
 *  libstack.so.1.0是realname,libstack.so.1是soname
 */

/* 1,虚拟内存管理控制物理内存的访问权限
 * 2,最主要的作用是让每个进程都有独立的地址空间。是指
 * 不同进程中的同一个VA被MMC映射到不同的PA,并且某一个进程中访问任何地址都不能访问到另外一个进程的数据。
 * 3,VA到PA的映射会给分配和释放内存带来方便。不连续的PA可映射为连续的VA。
 */

/* define
 * 如果一个程序文件中重复定义一个宏,C语言规定这些重复的宏定义必须一模一样
 *
 * #运算符用于创建一个字符串,#后应该跟一个形参
 * 注意,实参中如果包含字符串或字符常量,则宏展开之后,字符串的界定符"
 * 要替换成\",其中的\要替换成\\
 *
 * 宏定义中运算符##把前后两个预处理Token连接成一个Token。根据定义,##不能出现在宏定义开头或结尾
 * __VA_ARGS__
 * gcc有一种扩展语法,如果
 * ##运算符用在__VA_ARGS__前面,当__VA_ARGS__是空参数时,##前面的,被吃掉了
 */

#define sh(x) printf("n" #x "=%d, or %d\n", n##x,alt[x])
#define sub_z 26
sh(sub_z);

/* 展开后为 printf("nsub_z = %d, or %d\n",nsub_z, alt[26] */

/* #pragma预处理指示供编译器实现一些非标准的特性。
 * __FILE__展开为当前源文件名的字符串,
 * __LINE__展开为当前代码行的行号。
 * GCC引进了一个特殊的标识符
 * __func__,是一个变量名而不是标识名,不属于预处理。当前函数名的字符串。
 */

/*
 * Makefile
 *
 * 如果make执行的命令前加了@命令,则不显示命令本身而只显示它的结果。命令如果出错,就立刻终止。
 * 如果命令前加了-号,即使这条命令出错,make也会继续执行后续命令。
 *
 * 把clean声明为一个伪目标。 .PHONY: clean
 * make隐含规则(Implicit Rule)
 * #号表示单行注释,
 * $(CC)取变量CC的值,
 * $@ 的取值为规则中的目标,
 * $< 为规则中的第一个条件,
 * $? 为规则中所有比目标新的条件,组成一个列表,以空格分隔。
 * $^ 表示规则中的所有条件。
 * 用=号定义变量的延迟展开特性,有可能写出无限递归的定义,例如 CFLAGS =
 * $(CFLAGS) -O。如果希望make遇到变量定义时立即展开,可以用:=运算符。
 * 如果要定义一个变量的值是空格,可以
NULLSTR :=
SPACE := $(NULLSTR) # 注释前面有个空格。
*/

/* foo ?= $(bar)的意思是,如果foo没有定义过, foo =
 * $(bar),如果foo已经定义,则什么也不做
 *
 * += 运算符可以给变量追加值。+=根据前面是=还是:=再展开。
 * 例如:
 * objects := main.o
 * objects += $(foo)
 * 前面是 :=,则 如果foo没有定义,则 objects =
 * main.o。如果前面foo已经定义,则objects = main.o foo的值
 *
 */

/* 数组名做右值时自动转换成指向首元素的指针
 * 指向const变量的指针或者const变量的地址不可以传给指向非const变量的指针。
 * 如果要定义一个指针指向字符串字面值,这个指针应该是const char * 类型。
 * 错误例子:
 * char *p = "abcd";
 * *p = 'A';
 * p指向 .rodata段,不允许改写,但编译器不会报错,在运行时会出现段错误
 */

int a[10];
int (*pa)[10] = &a;
/* &a[0]表示数组a的首元素的首地址,而&a表示数组a的首地址。数值相同,类型不同。
*/

typedef int F(void);         /* 定义函数类型F */

F f, g;                      /* 正确,int f(void) */
//F h(void);                  /* 错误,不能返回函数类型 */
F *e(void);                  /* 正确,返回一个F*类型的函数指针 */

/* 传入参数,传出参数,Value-result参数 */

/* stdarg.h的一种实现。
 * 原理:可变参数中的参数从右向左依次压栈,第一个参数靠近栈顶,最后一个参数在栈底。对齐!!
 */

#ifndef _STDARG
#define _STDARG

typedef char *va_list;
#define va_arg(ap,T) \
        (* (T*)(((ap) += _Bnd(T, 3U)) - _Bnd(T, 3U)))
#define va_end(ap) (void)0
#define va_start(ap, A) \
        (void)((ap) = (char *)&(A) + _Bnd(A, 3U))
/*_Bnd使之对齐,注意后面的运算符优先级,是 ((sizeof(x) + (bnd)) & (~(bnd)) */
#define _Bnd(X, bnd) (sizeof(X) + (bnd) & ~(bnd))
#endif

void *memset(void *s, int c, size_t n);
void *memcpy(void *dest, const void *src, size_t n);
void *memmove(void *dest, const void *src, size_t n);
/* memmove可以拷贝两个重叠的内存区域。*/

char *strtok(char *str, const char *delim);
char *strtok_r(char *str, const char *delim, char **saveptr); /* 可重入posix ,saveptr是个 Value-result参数*/

// "r"    只读,文件必须存在
// "w"    只写,文件不存在就创建,截0
// "a"    追加,不存在就创建
// "r+"   读写,文件必须存在
// "w+" "a+",读写,文件不必存在
//

//从终端设备输入时有两种方法,可以表示文件结束。一种是开头输入EOF或行中输入两次EOF,一种是用Shell的Heredoc语法。 ./a.out << endif
// printf %a.b
// a为宽度,格式化后的最小长度。b为精度,对于字符串来说指定了格式化后的最大长度,对于浮点数来说指定了格式化后小数点右边的位数,对于整数来说指定了格式化后的最小位数。
//
//
long int strol(const char *nptr, char **endptr, int base);
double strtod(const char*nptr, char **endptr, int base);
// endptr指向未被识别的第一个字符
//