1、大端小端
大端和小端是指计算机在存储数据时的字节顺序问题。
大端字节序,也称为“高位在前”,即数据的高位字节存储在内存的低地址处,低位字节存储在内存的高地址处。不同字节从左往右依次存储。
小端字节序,也称为“低位在前”,即数据的低位字节存储在内存的低地址处,高位字节存储在内存的高地址处。不同字节从右往左依次存储。
举个例子,假设从内存地址0x0000开始存储数值0x12345678,那么:
在大端字节序中,内存的存储顺序为0x12 0x34 0x56 0x78(高位在前)。
在小端字节序中,内存的存储顺序为0x78 0x56 0x34 0x12(低位在前)。
在实际开发中,需要注意不同操作系统和处理器的字节顺序可能不同。当在网络中传输数据时,需统一对数据进行转换,以避免通讯时出现字节顺序错误的问题。
2、结构体大小的算法
结构体的大小取决于结构体中每个成员的大小和对齐方式。在32位的Linux系统中,结构体的对齐方式通常为4字节对齐。以下是计算结构体大小的基本算法:
-
对于每个成员变量,计算它的大小。这个大小一般可以用sizeof操作符来计算。
-
计算结构体的总大小,即每个成员变量的大小之和。
-
根据结构体对齐方式对总大小进行调整。如果某个成员变量大小不是对齐方式的整数倍,那么需要加上一些填充字节来保证对齐。填充字节的大小也要根据对齐方式来确定。
例如,在32位的Linux系统中,如果有一个结构体,它包含一个整型变量、一个字符型变量和一个浮点型变量,那么它的大小计算如下:
-
整型变量大小为4字节,字符型变量大小为1字节,浮点型变量大小为4字节。
-
结构体总大小为4 + 1 + 4 = 9字节。
-
在32位系统中,对齐方式为4字节对齐,所以需要在字符型变量后添加3字节的填充字节,让浮点型变量对齐。因此,结构体的最终大小为12字节。
需要注意的是,不同的系统和不同的编译器可能会有不同的对齐方式,所以同样的结构体在不同系统中的大小可能会有所差别。因此,在编写程序时,需要考虑到跨平台的问题,特别是在涉及到网络通信和数据存储时。
下面是一个在Linux下32位系统中定义结构体的例子:
```c
#include <stdio.h>
#include <stdlib.h>
typedef struct student {
char name[20];
int age;
float gpa;
} student_t;
int main() {
student_t student1;
student1.age = 22;
student1.gpa = 3.8;
sprintf(student1.name, "Tom");
printf("Name: %s\n", student1.name);
printf("Age: %d\n", student1.age);
printf("GPA: %f\n", student1.gpa);
return 0;
}
这个例子定义了一个名为`student`的结构体,包括了一个名为`name`的字符数组、一个名为`age`的整型变量和一个名为`gpa`的浮点型变量。在主函数中,定义了一个名为`student1`的`student_t`类型的结构体变量,并分别给`age`和`gpa`赋值,用`sprintf()`函数给`name`赋值,最后用`printf()`函数输出三个成员变量的值。
3、linux系统下,继承概念:
在Linux系统中,继承是指一个进程(或线程)可以从另一个进程(或线程)继承一部分资源,包括打开的文件描述符、环境变量、信号处理方式等等。继承可以简化代码编写,提高代码复用性,降低系统开销。
在Linux中,进程(或线程)创建时会继承父进程(或线程)的一些资源。其中,最重要的是文件描述符。当子进程从父进程继承了一个文件描述符时,子进程可以继续使用该文件描述符来进行文件读写操作,就好像子进程自己打开了这个文件一样。此外,子进程还会从父进程继承其他一些资源,例如进程环境变量、当前工作目录、信号处理方式等等。
继承还有一个重要的应用场景是在进程间通信(IPC)中。例如,父进程创建了一个管道用于与子进程进行通信。如果子进程从父进程继承了管道的文件描述符,那么它就可以使用该文件描述符向管道中写入数据,同时父进程也可以从该文件描述符中读取数据,从而实现进程间通信。
需要注意的是,尽管继承可以提高代码复用性,但它也可能带来一些问题。如果在继承的资源中有一个资源被意外地关闭或修改了,那么会影响到所有继承该资源的进程。因此,在使用继承时需要谨慎处理,保证继承的资源不会被误修改或关闭。
子进程在创建时会完全继承父进程的整个内存空间,包括程序代码、数据区域、栈以及各种资源,如文件描述符、信号处理方式、环境变量等。但是,子进程会有自己的独立的进程ID、资源使用情况和进程状态等。这些继承的资源是在子进程执行exec函数之前继承的,当执行exec函数时,子进程会将自己的内存空间替换成一个新的程序或命令,继承将不再起作用。
子进程默认会继承父进程的大多数资源,但也有一些是不会被继承的,包括:
1. 进程ID(PID)和父进程ID(PPID)。
2. 信号处理方式和信号处理函数。
3. CPU时间和资源使用情况。
4. 一些控制信息,如调度优先级和限制等。
5. IPC(进程间通信)相关的资源,如消息队列、共享内存和信号量等。
6. 内存映射区域和共享内存段的内容。
7. 已注册的报警定时器。
8. 环境变量等。
如果子进程需要这些资源,可以通过调用相关函数来创建或获取这些资源。
4、临界区
临界区是指在并发编程中被多个线程访问的共享资源区域,因为多个线程同时访问这个区域可能会引起竞争条件(例如读写冲突),从而导致程序逻辑错误或者系统故障。因此,在访问临界区时需要进行同步控制,以保证多个线程可以正确地访问和修改共享资源。一般来说,常见的同步控制方式有互斥锁、信号量、读写锁等。在同步控制的过程中,一次只有一个线程能够进入临界区,避免了多个线程同时修改共享资源的风险。
5、正则表达式
正则表达式是一种通用的字符串匹配表达式,可以用来判断一个字符串是否符合某种规则、搜索特定的字符串或者替换文本中的某些部分。不同的编程语言和工具支持的正则表达式语法可能略有不同,但其基本规则和用法类似,常见的一些正则表达式包括:
1. 字符匹配,例如匹配任意字符 "."、匹配多个字符 " *"、匹配单个字符 " ?" 等;
2. 字符组和范围,例如匹配任意数字 "\d"、匹配任意小写字母 "\l"、匹配任意大写字母 "\u"、定义字符组 "[]" 等;
3. 边界匹配,例如匹配行首 "^"、匹配行尾 "$"、匹配单词边界 "\b" 等;
4. 分组、引用和捕获,例如使用括号分组 "() "、使用反斜杠引用字符 "\1"、使用捕获变量 "$1" 等;
5. 替换和转义,例如替换匹配的字符 "s/old/new/"、转义特殊字符 "\"。
以上只是一些常见的正则表达式,实际使用时还需要根据具体场景和语言库的支持选择合适的正则表达式语法。
6、参数引用
参数引用传递(pass by reference)是一种参数传递方式,在这种方式下,函数参数是实际变量的引用(内存地址),函数对参数的修改会改变实际变量的值。这种方式通常比参数值传递(pass by value)更高效,特别是当参数很大时(例如数组)。
使用参数引用传递,可以在函数内部修改函数参数的值,从而实现对实际变量的修改。这个过程理解为“传递内存地址,而不是传递值”。在函数内部,可以通过引用参数访问实际变量,并进行修改。
在一些编程语言中(例如C++和C#),参数引用传递需要明确地使用“&”符号进行声明。在Java等一些语言中,一般采用对象引用传递(pass by reference of object),即传递一个指向对象的引用,从而可以在函数内部操作该对象的属性。在Python中,所有传递都是参数引用传递。
需要注意的是,使用参数引用传递的风险是可能会在函数内部修改参数的值,影响到调用函数的其他部分。因此,在使用参数引用传递时,需要谨慎使用,并保证理解清楚函数对参数的修改行为。