嵌入式面试2(c相关)

目录

1.C语言中static、const、volatile关键字用法区别;

static的用法(定义和用途)

const的用法(定义和用途)

volatile (英文意思为易变的) 作用和用法:

2.C语言中,const 和 static 的区别,char * const 和 const * char的区别

3.变量声明和定义的区别:

4.C语言编译后的内存分布

5.C语言内存分区,未定义的全局变存放在哪个区

6.静态局部变量存储在静态区,那么静态区的创建和消失是在什么时候?

7.变量加入static以后在内存中存储位置的变化

8.在windows里面运行了多个进程,其中一个进程执行完了,它的静态区会如何处理

9.连续调用同一个函数两次,它的局部变量初始化结果是否会一致?

10.静态局部变量与局部变量的区别?为什么局部变量未定义时,每次初始化的结果是不确定的?是个真随机数还是个伪随机数? 

11.快排时间复杂度怎么计算的

12.用熟悉的编译器手写栈和冒泡排序


1.C语言中static、const、volatile关键字用法区别;

static的用法(定义和用途)


1)用static修饰局部变量:使其变为静态存储方式(静态数据区),那么这个局部变量在函数执行完成之后不会被释放,而是继续保留在内存中。
2)用static修饰全局变量:使其只在本文件内部有效,而其他文件不可连接或引用该变量。
3)用static修饰函数:对函数的连接方式产生影响,使得函数只在本文件内部有效,对其他文件是不可见的(这一点在大工程中很重要很重要,避免很多麻烦,很常见)。这样的函数又叫作静态函数。使用 静态函数的好处是,不用担心与其他文件的同名函数产生干扰,另外也是对函数本身的一种保护机制。

const的用法(定义和用途)


const主要用来修饰变量、函数形参和类成员函数:
1)用const修饰常量:定义时就初始化,以后不能更改。
2)用const修饰形参:func(const int a){};该形参在函数里不能改变
3)用const修饰类成员函数:该函数对成员变量只能进行只读操作,就是const类成员函数是不能修改成员变量的数值的。被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。

1.const int a;
2.int const a;
3.const int *a;
4.int * const a;
5.int const * a const;

前两个的作用是一样,a是一个常整型数。

常指针从右往左读,以*为分界

第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。
第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。
最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。

volatile (英文意思为易变的) 作用和用法:


        一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量在内存中的值,而不是使用保存在寄存器里的备份(虽然读写寄存器比读写内存快)。
以下几种情况都会用到volatile:

并行设备的硬件寄存器(如:状态寄存器)
一个中断服务子程序中会访问到的非自动变量  
多线程应用中被几个任务共享的变量

2.C语言中,const 和 static 的区别,char * const 和 const * char的区别

const 关键字用于定义常量,即在程序运行期间不可修改的值。const 可以用于修饰变量、函数参数和函数返回值。使用 const 可以提高程序的可读性和安全性。

static 关键字用于定义静态变量或函数。静态变量在程序运行期间只会被初始化一次,而静态函数只能在当前文件中被调用。使用 static 可以控制变量和函数的作用域,避免命名冲突和不必要的访问。

char * const 和 const * char 的区别在于指针指向的内容是否可以被修改。char * const 表示指针本身是常量,即指针指向的地址不可修改,但指针指向的内容可以修改;而 const * char 表示指针指向的内容是常量,即指针指向的内容不可修改,但指针本身可以修改。

3.C语言字节对齐、结构体对齐

什么是字节对齐?

在C语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。在结构中,编译器为结构的每个成员按其自然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。

为了使CPU能够对变量进行快速的访问,变量的起始地址应该具有某些特性,即所谓的”对齐”. 比如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除.

字节对齐有什么作用?

需要字节对齐的根本原因在于CPU访问数据的效率问题。假设变量的地址不是自然对齐,取它的值的话需要访问多次内存,而如果变量在自然对齐位置上,则只要一次就可以取出数据。

正确处理字节对齐
  
   对于标准数据类型,它的地址只要是它的长度的整数倍就行了,而非标准数据类型按下面的原则对齐:
  数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。
  联合 :按其包含的长度最大的数据类型对齐。
  结构体: 结构体中每个数据类型都要对齐。

结构体各成员的起始位置相对于结构体变量的起始位置的偏移量,应该为该结构体成员类型所占字节数与pack(n)的n取最小值的倍数
• 结构体变量所占字节数应该是结构体各成员所占字节数的最大值与pack(n)的n取最小值

举例说明

例1

struct test
{
char x1;
short x2;
float x3;
char x4;
};

由于编译器默认情况下会对这个struct作自然边界(有人说“自然对界”我觉得边界更顺口)对齐,结构的第一个成员x1,其偏移地址为0,占据了第1个字节。第二个成员x2为short类型,其起始地址必须2字节对界,因此,编译器在x2和x1之间填充了一个空字节。结构的第三个成员x3和第四个成员x4恰好落在其自然边界地址上,在它们前面不需要额外的填充字节。在test结构中,成员x3要求4字节对界,是该结构所有成员中要求的最大边界单元,因而test结构的自然对界条件为4字节,编译器在成员x4后面填充了3个空字节。整个结构所占据空间为12字节。

例2

#pragma pack(1) //让编译器对这个结构作1字节对齐
struct test
{
char x1;
short x2;
float x3;
char x4;
};
#pragma pack() //取消1字节对齐,恢复为默认4字节对齐

这时候sizeof(struct test)的值为8。

变量声明和定义的区别:

变量声明只是变量的说明,在程序中不需要为其分配内存空间;而变量定义是为变量分配内存空间并初始化

4.C语言编译后的内存分布

C语言编译后的内存分布通常包括以下几个部分:

1. 代码段(Text Segment):也称为只读代码段,存放程序的机器指令。在程序运行时,代码段是只读的,不允许修改。

2. 数据段(Data Segment):存放已初始化的全局变量和静态变量。在程序加载时,数据段的大小是固定的。

3. BSS段(Block Started by Symbol):存放未初始化的全局变量和静态变量。在程序加载时,BSS段的大小是固定的,但是实际上并不占用磁盘空间,只是在程序运行时根据需要分配内存。

4. 堆(Heap):用于动态分配内存。在程序运行时,可以通过函数如malloc()和free()来动态地分配和释放堆内存。

5. 栈(Stack):用于存放函数的局部变量、函数参数和函数调用的上下文信息。栈是一种后进先出(LIFO)的数据结构,每次函数调用时,会在栈上分配一块内存,函数返回时会释放这块内存。

6. 环境变量区域:存放环境变量的值。

7. 命令行参数区域:存放命令行参数的值

5.C语言内存分区,未定义的全局变存放在哪个区

bss段:未初始化或初始化未0的全局变量和静态变量,具体体现为 一个占位符,不占用.exe文件空间,其内容由操作系统初始化/清零

6.静态局部变量存储在静态区,那么静态区的创建和消失是在什么时候?

静态区是在程序运行时由操作系统分配的一块内存空间,它的创建和消失与程序的生命周期有关。静态局部变量在程序运行时只会被初始化一次,它们的内存空间会一直存在直到程序结束。因此,静态区的创建和消失是在程序启动时创建,在程序结束时销毁。

7.变量加入static以后在内存中存储位置的变化

当一个变量被声明为static时,它的存储位置会从栈(stack)或堆(heap)转移到静态存储区(static storage area),也称为全局数据区(global data area)。

在栈或堆中声明的变量是在函数调用时动态分配的,当函数返回时,它们的存储空间会被释放。而在静态存储区中声明的变量则在程序运行期间一直存在,直到程序结束才会被释放。

因此,使用static声明的变量可以在多个函数之间共享,而不需要通过参数传递或全局变量来实现。此外,静态变量的初始值只会被赋值一次,即使函数被多次调用,它的值也会保持不变。

8.在windows里面运行了多个进程,其中一个进程执行完了,它的静态区会如何处理


当一个进程执行完毕后,操作系统会回收该进程的资源,包括其地址空间。在回收过程中,操作系统会释放该进程所占用的内存空间,包括静态区。释放静态区的具体处理方式取决于操作系统的实现。

一般情况下,操作系统会将静态区的内存空间标记为可用,以便其他进程可以重新使用。但是,静态区中的数据并不会立即被清除,而是等待下一个进程使用该内存空间时进行覆盖。因此,在下一个进程使用该静态区之前,原来进程的数据可能仍然存在于内存中。

9.连续调用同一个函数两次,它的局部变量初始化结果是否会一致?

一般情况下,连续调用同一个函数两次,它的局部变量初始化结果不会一致。这是因为每次函数调用时,局部变量都会重新初始化。如果函数中的局部变量没有被显式地初始化,那么它们的值将是未定义的,可能会导致不同的结果。但是,如果函数中的局部变量被显式地初始化为相同的值,那么它们的值将是一致的。

10.静态局部变量与局部变量的区别?为什么局部变量未定义时,每次初始化的结果是不确定的?是个真随机数还是个伪随机数? 

静态局部变量和局部变量的区别在于它们的生命周期和作用域不同。静态局部变量在函数内部定义,但是它们的生命周期是整个程序运行期间,而不是函数调用期间。而局部变量的生命周期只在函数调用期间存在。

当局部变量未定义时,每次初始化的结果是不确定的。这是因为未初始化的局部变量的值是未定义的,它们的值取决于它们在内存中的位置和之前存储在该位置的值。因此,每次初始化的结果可能是不同的。

这些值通常是伪随机数,而不是真随机数。伪随机数是通过算法生成的数字序列,看起来像是随机的,但实际上是可预测的。在C语言中,未初始化的局部变量通常会被分配为内存中的随机值,这些值通常是伪随机数。

11.快排时间复杂度怎么计算的

快速排序的时间复杂度可以通过递归树来计算。在最坏情况下,每次划分都只能将序列分成一个元素和n-1个元素,这样递归树的深度就是n,每层的时间复杂度都是O(n),因此最坏情况下的时间复杂度是O(n^2)。

在平均情况下,每次划分都能将序列分成大小大致相等的两个子序列,递归树的深度是logn,每层的时间复杂度都是O(n),因此平均情况下的时间复杂度是O(nlogn)。

最好情况下,每次划分都能将序列分成大小相等的两个子序列,递归树的深度是logn,每层的时间复杂度都是O(n),因此最好情况下的时间复杂度也是O(nlogn)。

因此,快速排序的时间复杂度在最坏情况下是O(n^2),在平均情况下和最好情况下都是O(nlogn)。

12.用熟悉的编译器手写栈和冒泡排序

#define MAX_SIZE 100

typedef struct {
    int data[MAX_SIZE];
    int top;
} Stack;

// 初始化栈
void initStack(Stack *stack) {
    stack->top = -1;
}

// 判断栈是否为空
int isEmpty(Stack *stack) {
    return stack->top == -1;
}

// 判断栈是否已满
int isFull(Stack *stack) {
    return stack->top == MAX_SIZE - 1;
}

// 入栈
void push(Stack *stack, int value) {
    if (isFull(stack)) {
        printf("Stack is full. Cannot push element.
");
        return;
    }
    stack->data[++stack->top] = value;
}

// 出栈
int pop(Stack *stack) {
    if (isEmpty(stack)) {
        printf("Stack is empty. Cannot pop element.
");
        return -1;
    }
    return stack->data[stack->top--];
}

// 获取栈顶元素
int peek(Stack *stack) {
    if (isEmpty(stack)) {
        printf("Stack is empty. Cannot peek element.
");
        return -1;
    }
    return stack->data[stack->top];
}

int main() {
    Stack stack;
    initStack(&stack);

    push(&stack, 1);
    push(&stack, 2);
    push(&stack, 3);

    printf("Top element: %d
", peek(&stack));

    printf("Popped element: %d
", pop(&stack));
    printf("Popped element: %d
", pop(&stack));

    printf("Top element: %d
", peek(&stack));

    return 0;
}
void bubble_sort(int arr[], int n) {
    int i, j, temp;
    for (i = 0; i < n - 1; i++) {
        for (j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

int main() {
    int arr[] = {5, 2, 8, 3, 1, 6};
    int n = sizeof(arr) / sizeof(arr[0]);
    int i;
    printf("Before sorting: ");
    for (i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("
");
    bubble_sort(arr, n);
    printf("After sorting: ");
    for (i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("
");
    return 0;
}

你可能感兴趣的:(算法,数据结构)