C语言面经

发布于:2024-04-25 ⋅ 阅读:(27) ⋅ 点赞:(0)
25.类型相同的两个指针之间不能进行的运算

指针主要用于存储变量的内存地址。对于同类型的指针变量之间,有一些规则:

        a. 小于运算(<):指针间的小于比较是基于它们指向的内存地址。地址较小的指针在小于比较中被认为是较小的。

        b. 赋值运算(=):指针可以赋值给另一个相同类型的指针。这样做会使得两个指针指向同一个地址。

        c. 减法运算(-):两个指向同一数组的指针进行减法运算会得到一个整数,表示它们之间的元素差距。这里的减法结果不是一个地址,而是一个表示距离的整数值。

        d. 加法运算(+):两个指针之间不能进行加法运算。指针加法没有定义,因为这不会产生有意义的结果,也可能导致程序崩溃或安全问题。

26、如何在C语言中设置、清除、切换和单位检查单个位?
  • 设置特定位:将一个数的特定位设置为1,可以使用“按位或”(|)运算符。具体语法:
number |= (1UL << nthPosition);

        这里,其中UL代表Unsigned Long1UL表示一个无符号数据1。将这个数向左移动nthPosition位,将1放置在我们想要设置的位上,然后使用“按位或”(|)运算符将其与原始数进行组合。任何数与1进行“按位或”运算的结果都是1。

  • 清除特定位:要清除一个数的特定位,即设置为0,使用“按位与”(&)运算符与一个特殊的位掩码进行运算:
number &= ~(1UL << nthPosition);

1左移nthPosition位,然后使用按位非(~)操作创建一个在该位为0的掩码,其他位为1。与原数进行“与”运算后,该位被清零

  • 检查特定位:要检查一个数的特定位是否设置为1,使用“按位与”(&)运算符:
bit = number & (1UL << nthPosition);

        将1左移至目标位产生掩码,然后与原数进行“与”运算。如果目标位为1,则结果非零;如果为0,则结果为零。

  • 切换特定位:若要切换整数的特定位的值,使用“按位异或”(^)运算符:
number ^= (1UL << nthPosition);

        将1左移nthPosition位产生掩码,然后与原数进行“异或”运算。这将切换目标位的值,因为“异或”运算符会将相同的位转为0,不同的位转为1。

28.C语言动态内存分配的函数

        "C语言提供了几个强大的函数来进行动态内存分配,使得程序员可以在程序执行期间根据需要分配或释放内存。这些函数是 malloc(), calloc(), realloc(), 和 free()

  1. malloc() 函数用于分配指定大小的内存块。它不会初始化内存,所以分配的内存中可能包含垃圾值。如果成功,它返回指向分配的内存的指针;如果失败,则返回空指针。其语法是 ptr = (cast-type*)malloc(byte-size),这里 ptr 是指向分配的内存的指针,byte-size 是要分配的字节数。
  2. calloc() 函数malloc() 类似,但有两个主要区别:它同时分配内存并将其初始化为零。其语法是 ptr = (cast-type*)calloc(n, element-size),其中 n 是要分配的元素数量,而 element-size 是每个元素的大小。这对于初始化数组特别有用。
  3. realloc() 函数用于重新调整之前分配的内存块的大小。这是在动态内存管理中非常有用的功能,允许增加或减少已分配内存的大小。如果在原有内存块附近没有足够的空间进行扩展,它会分配一个新的内存块,复制旧数据到新位置,并释放旧内存块。其语法是 ptr = realloc(ptr, newsize)newsize 是新的大小。
  4. free() 函数用于释放之前通过 malloc()calloc()realloc() 分配的内存块。一旦内存被释放,原有指针所指向的内存区域就不再有效。释放未使用的内存是避免内存泄漏的重要步骤。其语法非常简单:free(ptr)
29.const

        在C语言中,const关键字用于声明一个常量,即在程序执行过程中其值不能被修改的变量。const修饰的变量通常存储在只读存储段,比如只读数据段(.rodata)或文本段(.text),这些内存区域在程序运行时是不允许写入的。

  1. const修饰的变量是只读的。如果尝试修改一个const变量的值,编译器将报错,因为这会违反变量的只读属性。
  2. const修饰数组时,数组中的所有元素都是不可修改的。如果尝试修改数组中的一个const元素,将会引起编译错误。
  3. const修饰指针有不同的含义,这取决于const关键字放置的位置。例如:
  • const int *p意味着指针指向的int值不可以修改,但指针本身可以修改指向其他的int变量。
  • int *const p意味着指针p是一个常量,即p不能指向别的地址,但是p指向的值可以修改。
  • const int *const p意味着指针p不能修改指向,同时p指向的int值也不能被修改。                  当一个const变量被初始化后,它的值就固定了,不能再被修改。这意味着const变量通常需要在声明的同时初始化。
34.原码反码补码的计算

        首先说原码,最前面是符号位,0表示正数,1表示负数,其他的就是普通的二进制数。然后是反码是数值存储的一种,它的正数与原码相同。但负数呢,除了最高位还是1以外,其他位上的1变成0,0变成1。多用于系统环境设置,如在Linux这类系统里目录和文件的默认权限的设置umask,就是使用反码原理。最后来说补码,计算机一律用它来存数和算数。因为用补码,正数负数加减法就能用一套规则来解决,简化了很多。计算补码,正数不用变,负数就把反码加个1就是了。所以,如果要我来计算一个负数的补码,我会先写出它的原码,然后变成反码,最后再给反码加1”

36.逗号表达式

        运算过程是从右向左逐个计算,逗号表达式的值为最后一个表达式的值,逗号运算符的优先级在所有运算符中最低。

40. 如何解决头文件中的重复包含问题?

        为了解决头文件重复包含的问题,通常有两种方法来避免同一个头文件被包含(include)多次导致的编译错误和性能问题:

  1. Include Guards: 这是一种传统的预处理器技巧,通过定义特定的宏来防止头文件的内容被多次包含。典型的做法是使用#ifndef#define,和#endif指令来定义一个只有当特定宏未定义时才允许包含头文件内容的条件。
cCopy code
// A.h文件的示例
#ifndef A_H_
#define A_H_
// 头文件内容
#endif // A_H_
  1. #pragma once: 这是一种现代和非标准的预处理器指令,告诉编译器只包含头文件一次。它简洁且通常可以跨多个编译器工作,但不是所有的编译器都支持它。
cCopy code
// A.h文件的示例
#pragma once
// 头文件内容

        在多数情况下,#pragma once提供了一种简单而清晰的方法来防止头文件的重复包含。但在跨平台的项目中或为了确保最佳的兼容性,传统的Include Guards方法是更可靠的选择。

41. 为什么程序的指令和程序数据分开

        程序被加载到内存中之后,可以将数据和代码分别映射到两个内存区域。由于数据区域对进程来说是可读可写的,而指令区域对程序来讲是只读的,所以分区之后呢,可以将程序指令区域和数据区域分别设置成只读或可读可写。这样可以防止程序的指令有意或者无意被修改当系统中运行着多个同样的程序的时候,这些程序执行的指令都是一样的,所以只需要内存中保存一份程序的指令就可以了,只是每一个程序运行中数据不一样而已,这样可以节省大量的内存。

44.C 从代码到可执行二进制文件的过程

一个C程序从源码到执行文件,有四个过程,预编译、编译、汇编、链接。

预编译:这个过程主要的处理操作如下:

  • 1)将所有的 #define 删除,并且展开所有的宏定义
  • 2)处理所有的条件预编译指令,如 #if、#ifdef
  • 3)处理 #include 预编译指令,将被包含的文件插入到该预编译指令的位置。
  • 4)过滤所有的注释(注释被编译器理解成空格)
  • 5)添加行号和文件名标识。

编译:这个过程主要的处理操作如下:

  • 1)词法分析:将源代码的字符序列分割成一系列的记号
  • 2)语法分析:对记号进行语法分析,产生语法树。
  • 3)语义分析:判断表达式是否有意义,
  • 4)代码优化:源代码级别的一个优化过程。
  • 5)目标代码生成:由代码生成器将中间代码转换成目标机器代码,生成一系列的代码序列汇编语言表示。
  • 6)目标代码优化:目标代码优化器对上述的目标机器代码进行优化:寻找合适的寻址方式、使用位移来替代乘法运算、删除多余的指令等。

汇编:这个过程主要是将汇编代码转变成机器可以执行的指令,

链接:将不同的源文件产生的目标文件进行链接,从而形成一个可以执行的程序。

45.为什么要生成汇编,而不是直接从源文件编译成机器指令?
  • 1)汇编语言作为机器指令的助记符,调试以及优化起来都会比较方便;
  • 2)汇编到机器指令的过程是由硬件完成的,是一个自动过程,让硬件来完成效率较高;
  • 3)如果要将源文件直接转换成机器指令,那么编译器编写者就必须要非常数量机器码,这是一个比较困难且低效的过程。