C语言中struct和union的区别
struct和union都是常见的复合结构。
- 结构体和联合体虽然都是由多个不同的数据类型成员组成的,但不同之处在于联合体中所有成员共用一块地址空间,即联合体只存放了一个被选中的成员,结构体中所有成员占用空间是累加的,其所有成员都存在,不同成员存放在不同的地址。在计算一个结构型变量的总长度时,其内存空间大小等于所有成员长度之和(需要考虑字节对齐),而在联合体中,所有成员不能同时占用空间,它们不能同时存在,一个联合体变量的长度等于其最长的成员长度。
- 对于联合体的不同成员赋值,将会对它的其它成员重写,原来成员的值就不存在了,而对结构体的不同成员赋值是互不影响的。
DATA占用20字节,而union中double占了8个字节,因此union要8个字节对齐,因此大小为24。
data = 4 + 24 + 8 = 36,按8字节对齐,40字节。
所以结果为64字节。
左值和右值是什么?
左值是指可以出现在等号左边的变量或表达式,它最重要的特点就是可写。也就是说,它的值是可以被修改的,如果一个变量或表达式的值不能被修改,那么就不能作为左值。
右值是指可以出现在等号右边的变量或表达式,它最重要的特点是可读,一般的使用场景是把一个右值赋给一个左值。
通常,左值可以作为右值,但右值不一定是左值。
什么是短路求值
对于或运算,如果第一个表达式为真,不需要执行第二个表达式。
对于与运算,如果第一个表达式为假,不用执行后面语句。
++a和a++有什么区别?
a++;
int temp = a;
a = a + 1;
return temp;
++aa = a+1;
return a;
后置自增运算符需要把原来变量的值复制到一个临时的存储空间,等运算结束后才返回这个临时变量的值。
所以前置自增运算符效率比后置自增运算符要高
C语言中内存分配的方式有几种?
- 静态存储区分配。
内存分配在程序编译之前完成,且在程序的整个运行期间都存在,例如全局变量,静态变量等。 - 栈上分配。
函数执行时,函数内的局部变量的存储单元在栈上创建,函数执行结束时这些存储单元自动释放。 - 堆上分配。
由程序员手动分配和释放。
堆与栈有什么区别
- 申请方式
栈的空间由操作系统自动分配/释放,堆上的空间手动分配/释放。 - 申请大小的限制
栈空间有限,在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存区域。栈顶地址和最大容量是系统预先设定好的,如果申请空间超过剩余栈空间时,将提示overflow,因此,能从栈获得的空间较小。
堆是向高地址扩展的数据结构,是不连续的内存区域。由于系统是使用链表来存储空间内存地址的,堆的大小受限于计算机系统中有效的虚拟内存,因此,堆获得的空间比较灵活,也比较大。 - 申请效率
栈是由系统自动分配,速度较快,但程序员无法控制。
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。
栈的作用
- 用来存储临时变量,包括函数参数和函数内部定义的临时变量。函数的返回地址,寄存器均保存在栈中,函数返回后从栈中恢复寄存器的值。
- 每个线程都有自己的栈,用来存储本线程运行时各个函数的临时变量和维系函数调用和返回时的函数调用关系。
C语言函数参数压栈顺序是怎样的?
从右至左。
C语言参数入栈顺序的好处就是可以动态变化参数个数。
自左向右的入栈方式,最前面的参数被压在栈底。除非知道参数个数,否则无法通过栈指针的相对位移求得最左边的参数。
函数指针和指针函数区别
函数指针
如果程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。而且函数名就是这个地址。
既然是地址就可以定义一个指针变量来存放,这个指针变量就叫做函数指针变量。
int (*p)(int, int);
指针函数
函数的返回值是指针变量。
int *func(int, int);
易错题型
sizeof既是关键字,也是运算符。
%取余运算符只能用于整型,A%B——A、B都必须为int类型。
BSS段:通常是用来存放程序中未初始化的全局变量和静态变量的一块内存区域。是可读写的,在程序执行之前,BSS段就会自动清零。
初始化列表的初始化顺序是按照成员声明顺序而来,先执行a(b),此时b没有初始化,因此为随机值,b为12。
对于对象 A,包含一个虚函数指针,因此对齐到 8 字节,然后有 2 个 char 型,最后补齐到 8 字节整倍数,因此其长度为 16 。
在Linux系统中,可以用来查找可执行文件的是?
- whereis:搜索可执行文件、联机帮助文件、源代码文件。
- locate所有文件
- which可执行文件
快速排序两趟结果,起码有2个元素就位。
文件共享
同一个文件(譬如磁盘上的同一个文件,对应同一个inode)被多个独立的读写体同时进行IO操作。
多个独立的读写体大家可以将其简单理解为对应同一个文件的多个不同的文件描述符,譬如多次打开同一个文件所得到的多个不同的fd。
多个进程间实现文件共享
原子操作
O_APPEND:移动指针到末尾+写入数据是原子操作。
pread()和pwrite():移动指针+写入是原子操作。但不更新文件表中的当前位置偏移量。
O_EXCL:判断文件是否存在+创建文件是原子操作。
截断文件
int truncate(const char*name, length);int ftruncate(int fd, off_t length);
将文件截断为参数length指定的字节长度,如果文件目前的大小小于参数length指定的大小,则多余的数据将被丢失,如果文件大小小于参数length指定的大小,则将其进行扩展为空字节。
fcntl和ioctl函数
对一个已经打开的文件描述符执行一系列控制操作。
int fcntl(int fd, int cmd, ...);
标准I/O库
标准C库当中用于文件I/O操作相关的一套库函数,使用标准I/O需要包含头文件。
标准I/O与文件I/O之间的区别:
- 标准I/O是库函数,文件I/O是系统调用。
- 标准I/O是对文件I/O的封装。
- 标准I/O比文件I/O有更好的可移植性。
- 标准I/O在效率上要优于文件I/O。
标准I/O使用FILE指针作为文件句柄,与文件I/O中的文件描述符相似。
标准I/O使用fopen()函数打开。
man 3 fopen()
fopen()函数新建文件时无法手动指定文件的权限,但有一个默认值0666.
printf函数是将用户的数据写入到标准输出(1)。
write(1, "Hello World\n", 12);
这5个函数都是可变参数,它们都有一个共同的参数format,这是一个字符串,称为格式控制字符串,用于指定后续的参数是如何进行格式转换,所以才把这些函数称为格式化输出。
printf()函数用于将格式化数据写入到标准输出;
fprintf()和dprintf()都是将格式化数据写入到指定文件中,fprintf()使用FILE指针指定对应的文件,dprintf()使用文件描述符fd指定对应的文件;
sprintf()和snprintf()函数可将格式化数据存储在用户指定的缓冲区buf中。sprintf()函数会在字符串末尾加上一个字符串终止字符’\0’;
%o:输出八进制表示的整数。
%u:输出无符号十进制。
%x/X:输出无符号十六进制
width:最小输出宽度,用十进制来表示输出的最小数,若实际的输出位数大于width,以实际的位数进行输出;若实际的位数小于指定输出的最小位数,则flags补0。
printf("%06d",1000);
格式化输入
scanf()函数用于将用户输入(标准输入)的数据进行格式化转换;fscanf()函数从FILE指针指定文件中读取数据,并将数据进行格式化转换;
sscanf()函数从参数str所指向的字符串中读取数据,并将数据进行格式化转换。
I/O缓冲
出于速度和效率的考虑,系统I/O调用(即文件I/O,open,read等)和标准C语言库I/O函数(即标准I/O函数)在操作磁盘文件时会对数据进行缓冲。
read()和write()系统调用在进行文件读写操作的时候不会直接访问磁盘设备,而是仅仅在用户空间缓冲区和内核缓冲区之间复制数据。
譬如调用write()函数将5个字节数据从用户空间内存拷贝到内核空间的缓冲区中。
write(fd, "Hello", 5);
调用write()后仅仅是将这5个字节拷贝到了内核空间的缓冲区中,拷贝完成之后函数就返回了,在后面的某个时刻,内核会将其缓冲区中的数据写入到磁盘设备中。
由此可知,系统调用write()与磁盘操作并不是同步的,write()并不会等数据真正写到磁盘之后再返回。
这个缓冲区称为文件I/O的内核缓冲。
读文件时,内核会从磁盘设备中读取文件的数据并存储到内核的缓冲区中,当调用read()函数读取数据时,read()调用将从内核缓冲区中读取数据,直至把缓冲区中的数据读完,这时,内核会将文件的下一段内容读入到内核缓冲区中进行缓存。
这个内核缓冲区称为文件I/O的内核缓冲。为了提高文件I/O的速度和效率,使得系统调用read()等操作更快速,不需要等待磁盘操作,磁盘操作通常是比较缓慢的。
刷新文件I/O的内核缓冲区
强制将文件I/O内核缓冲区中缓存的数据写入到磁盘设备中,对于某些应用来说,可能是很有必要的,应用程序在进行某些操作之前,必须要确保前面步骤调用write()写入到文件的数据已经真实写入到了磁盘中,例如一些数据库的日志进程。
当我们在Ubuntu系统下拷贝文件到U盘时,文件拷贝完成之后,通常在拔掉U盘之前,需要执行sync命令进行同步操作,这个同步操作就是将文件I/O内核缓冲区中的数据更新到U盘硬件设备,如果没有执行,U盘中的文件可能还没来得及拷贝或没有完全拷贝。
int fsync(int fd);
系统调用fsync()将参数fd所指文件的内容数据和元数据写入磁盘,只有在对磁盘的写入操作完成之后,fsync()函数才会返回。
元数据是用于记录文件属性相关的数据信息,譬如文件大小、时间戳、权限等信息。
1K x 1K = 1M
系统调用sync()会将所有文件I/O内核缓冲区中的文件内容数据和元数据全部更新到磁盘设备中。
在Linux系统中,调用sync()函数仅在所有数据写入到磁盘设备之后才会返回;
调用open()函数时指定一些标志也会影响到文件I/O内核缓冲,譬如O_DSYNC和O_SYNC标志。
O_DSYNC标志类似于调用fdatasync()函数进行数据同步。
O_SYNC类似于fsync()