c++ 笔试及基础

本文深入探讨C/C++中的内存管理细节,包括全局变量的初始化与未初始化的区别,不同内存区域如栈区、堆区、全局区的特性,以及sizeof和strlen函数的使用区别。同时,文章解析了野指针的成因与预防,线程安全问题,以及一些常见的编程面试题解答。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

古北笔试题

1、全局变量初始化和未初始化有什么区别

编译器在编译的时候针对这两种情况会产生两种符号放在目标文件的符号表中,对于初始化的,叫强符号,未初始化的,叫弱符号
连接器在连接目标文件的时候,如果遇到两个重名符号,会有以下处理规则:
1、如果有多个重名的强符号,则报错。
2、如果有一个强符号,多个弱符号,则以强符号为准。

3、如果没有强符号,但有多个重名的弱符号,则任选一个弱符号

未初始化的符号在目标文件的bss段中,而初始化的符号在data段中

  1. (int*)malloc(sizeof(int)) 将申请得到的空间地址转换成了int类型空间地址,最后就可以赋值给指向int型空间的p指针了;
  2. C/c++变量的存储位置

一个由 c/c++编译过的程序占用的内存分为一下几个部分

  1. 、栈区 stack :由编译器自动分配释放存放函数的参数值,局部变量的值等。这个栈的操作方式类似于数据结构中的栈
  2. 堆区 heap :一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,它与数据结构中的堆是两回事,分配方式类似于链表
  3. 全局区(静态)static : 全局变量和静态变量的存储是放在一块的。初始化的全局变量和静态变量在一块区域未初始化的全局变量和静态变量又放在相邻的另一块区域中。程序结束后由系统释放
  4. 文字常量区 :常量字符串放在这里。程序结束后由系统释放
  5. 程序代码区 : 存放函数体的2进制代码

子://main.cpp

int a = 0; 全局初始化区

char *p1; 全局未初始化

Main()

{

Int b;//栈

Char s[]=abc;//栈

Char *p2;//栈

Char *p3=123456;//123456\0;//在常量区;p3在栈上

Static int c=0;//全局(静态)初始化区域

P1=(char*)malloc(10);

P2=(char*)malloc(20);//分配得来10和20字节的区域就在堆区

Strcpy(p1,123456);//123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方

}

  1. Sizeof()和strlen()
  1. 对于直接的调用sizeof,如果是数组首地址,sizeof会输出数组的元素个数。如果是一个指针的话,则输出在该系统中地址的字节宽度。
  2. 如果是通过函数传递过来的地址,那么sizeof会把数组首地址和指针无差别当成是地址。
  3. sizeof是算符,strlen是函数。
  4. sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以''\0''结尾.
  5. 数组做sizeof的参数不退化,传递给strlen就退化为指针了
  6. char str[20]="0123456789";
  7. 静态数组处理的结果

int a=strlen(str); //a=10; >>>> strlen 计算字符串的长度,以结束符 0x00 为字符串结束。  

int b=sizeof(str); //而b=20; >>>> sizeof 计算的则是分配的数组 str[20] 所占的内存空间的大小,不受里面存储的内容改变。

  1. 对指针

char* ss = "0123456789";

sizeof(ss) 结果 4>>>>ss是指向字符串常量的字符指针,sizeof 获得的是一个指针的值所占的空间,应该是长整型的,所以是4

sizeof(*ss) 结果 1>>>> *ss是第一个字符 其实就是获得了字符串的第一位'0' 所占的内存空间,是char类型的,占了 1 位

Strlen(ss)= 10 >>>> 如果要获得这个字符串的长度,则一定要使用strlen

sizeof返回对象所占用的字节大小. //正确

strlen返回字符个数. //正确

在使用strlen时,有一个很特别的情况,就是数组名到指针蜕变,

char Array[3] = {'0'};

sizeof(Array) == 3;

char *p = Array;

strlen(p) == 1;//sizeof(p)结果为4

Size_t strlen_a(const char *str)

{

Size_t length=0;

While(*str++)

++lengrh;

Return length;

}

  1. strcpy()

 Void test()

{

Char *a=abcdef;

Char b[4];

 

Strcpy(b,a);

printf("%s\n",b);

//Printf(%s\n,a);加上这一句时执行会出错,因为a   expression cannot be evaluated(表达式无法被求值;

}上述函数执行,会有什么问题?B的内容是什么?B的内容abcdef;a

 

  1. Strcpy函数原型

Char *strcpy(char *dst,const char*src);

char * strcpy(char *dst,const char *src)     

{  

    if((dst==NULL)||(src==NULL))  

           return NULL;   

    char *ret = dst; //[1]  

    while ((*dst++=*src++)!='\0'); //[2]  

    return ret;//[3]  

}  

  1. const修饰:源字符串参数用const修饰,防止修改原字符串;
  2. 空指针检查:源指针和目的指针都有可能会出现空指针的情况,所以应该对其进行检查;
  3. 为什么要设置ret 指针以及返回ret指针的位置[3],由于目的指针dst已经在进行移动了,所以用辅助指针ret表明首指针;
  4. 以上所示[2]处,为简单的字符串的复制过程,正好表明strcpy函数遇到'\0'将会停止
  5. strcpy只用于字符串复制,并且它不仅复制字符串内容之外,还会复制字符串的结束符

(6)

 

可以看出b输出的abcdsse;a输出的为sse;a、b的地址连续,而strcpy要读到\0才停止复制,所以就一直接下去读,知道\0;a输出sse是因为后面就是\0;

 

第一张图,输出abcdsse;abcdsse;然后程序报错Access Violation 意味着你的程序正在试图访问一块不在有效的内存。

将第二张图中的sprintf(b,sizeof(b),%s,a);执行将报错;

  1. char *=0;a++

 解释:char a=0;就是说a等于0这个字符(0)。而零的ascii码值为48.当把a当做整型运算的时候,a取值为48.  char* a=0,只是让指针指向一块字符型数据的区域,而这个区域存储的值是0;

 

 

 

 

福瑞泰特笔试题

  1. 有一个变量int a=0;两个线程同时进行+1操作,每个线程加100次,不加锁,最后a的值是()?

单核 [100,200]  多核[2,200]

i++不是原子操作,也就是说,它不是单独一条指令,而是3条指令:

1、从内存中把i的值取出来放到CPU的寄存器中

2、CPU寄存器的值+1

3、把CPU寄存器的值写回内存

如果是单线程操作,i++毫无问题;但是在多核处理器上,用多线程来做i++会有什么问题呢?

我再仔细地重复一遍问题:进程有一个全局变量i,还有有两个线程。每个线程的功能,就是循环100次,执行i++。问线程代码全部执行完毕后,i的值是否一定是200?如果不是,它的最大最小值是多少?

=========分析=======

i++是由3条指令构成的运算操作,两个线程在i变量上共计需要执行100(次循环)*3(条指令)*2(个线程)=600条指令,这600条指令在某种排列下会导致最终i的值仅为2。

(下面是我复制过来的)

假设两个线程的执行步骤如下: 

1. 线程A执行第一次i++,取出内存中的i,值为0,存放到寄存器后执行加1,此时CPU1的寄存器中值为1,内存中为0;

2. 线程B执行第一次i++,取出内存中的i,值为0,存放到寄存器后执行加1,此时CPU2的寄存器中值为1,内存中为0;

3. 线程A继续执行完成第99次i++,并把值放回内存,此时CPU1中寄存器的值为99,内存中为99;

4. 线程B继续执行第一次i++,将其值放回内存,此时CPU2中的寄存器值为1,内存中为1;

5. 线程A执行第100次i++,将内存中的值取回CPU1的寄存器,并执行加1,此时CPU1的寄存器中的值为2,内存中为1;

6. 线程B执行完所有操作,并将其放回内存,此时CPU2的寄存器值为100,内存中为100; 

7. 线程A执行100次操作的最后一部分,将CPU1中的寄存器值放回内存,内存中值为2;

2、开发c代码时,经常看到如下类型的结构体定义:

typedef struct list_t

{

struct list_t *next;

struct list_t *prev;

char data[0];

}list_t;

Sizeof(list_t)为8;因为 sizeof(char data[0])为0;值得说明的是,在结构体之外不能类似定义一个大小为0的数组;

结构体里面尾部定义一个大小为0的数组的作用:构成缓冲区

数组名就代表了该结构体后面数据的起始地址(而且无需初始化,不占空间) 而如果用指针的话,我们还要初始化,而且还要占空间(好象是int类型长度的空间)

例如:

定义一个结构体如下:
typedef struct buffer_s {
int a;
int buffer_no;
int flags;
unsigned char data[0];
} buffer_t;

buffer_t *buf;
int buf_len/*缓冲区数据的长度*/

/*直接把buffer的结构体跟存放数据的内存一起分配了*/
buf = (buffer_t *) malloc(sizeof(buffer_t) + buf_len);

buf->data = "我就是缓冲中的内容\n";

buf->data = "我就是缓冲中的内容\n";属于越界访问,但是我们把结构体后面的buf_len个长度的空间也一起申请了,所以该访问是合法的!

 

3、一个栈的入栈序列为ABCDEF,则不可能的出栈序列是

 

一个栈的入栈序列为ABCDEF,则不可能的出栈序列是(D) 
A、DEFCBA  B、DCEFBA  C、FEDCBA 
D、FECDBA  E、ABCDEF  F、ADCBFE

遵守原则:任何出栈的元素后面出栈的元素必须满足以下三点: 
1、在原序列中相对位置比它小的,必须是逆序; 
2、在原序列中相对位置比它大的,顺序没有要求; 
3、以上两点可以间插进行。

例如:d选项中,ECDBA在原序列都是比F小的,所以要逆序排列EDCBA

主要是考虑栈的核心思想是先进后出,并且需要注意入栈和出栈的顺序是未知的,例如你可以先入栈ABCD,然后出栈D,然后入栈E,出栈E,入栈F,出栈F,然后CBA依次出栈,也就是A选项的情况。

4、while(x)

{

Count++;

x=x&(x-1);//统计x中1的个数

}

延伸:判断一个数(x)是不是2的n次方;

int func(int x)
{
  if((x&(x-1))== 0)//判断一个数(x)是不是2的n次方;
      return 1;
   else
       return 0;
}

  1. char str1[10]; str1="string";不正确的赋初值

str1,数组名是一个地址常量,不是变量,不能作为左值被赋值。

  1. 野指针

 野指针指向一个已删除的对象或未申请访问受限内存区域的指针。与空指针不同,野指针无法通过简单地判断是否为 NULL避免

成因:指针变量未初始化

任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。如果没有初始化,编译器会报错" 'point' may be uninitializedin the function "。

折叠指针释放后之后未置空

有时指针在free或delete后未赋值 NULL,便会使人以为是合法的。别看free和delete的名字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。此时指针指向的就是"垃圾"内存。释放后的指针应立即将指针置为NULL,防止产生"野指针"。

折叠指针操作超越变量作用域

不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。示例程序如下:函数 Test1 在执行语句 p->Func()时,p 的值还是 a 的地址,对象 a 的内容已经被清除,所以 p 就成了"野指针" 。

 

招银网络笔试

静态对象

  1. 什么是静态对象?对象的存储方式是静态的。

 局部的静态对象和类的静态对象https://www.cnblogs.com/chengkeke/p/5417376.html(后续总结)https://www.cnblogs.com/kiplove/p/6875371.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值