记一次阿里电面经历

2019-04-13 13:33发布

昨天下午(3/19)三点多钟,接到了一个杭州的电话,是阿里的。问我是否方便聊聊,我说我在上课,四点下课。然后他就四点多钟的时候又打了一次过来。

项目经历

上来就问我有无大型项目的经历,不好意思,我说无。。。又问我代码量如何,我说之前有经常刷ACM的题目,所以代码量还可以。

C语言变量

问:“函数中的局部变量保存在哪里?”  答:“栈” 问:“函数中的局部静态变量保存在哪里?” 答:“静态区。。” 问:“局部静态变量和全局静态变量有不同吗,不同点在哪里?” 答:“。。没太大不同,都存在一起。。” 问:“不是问的存储位置,其他方面呢?” 答:“哦,可视的范围不同。全局静态变量全局可见,局部静态变量只有函数内部可见。” 问:“全局变量和全局静态变量有何不同” 答:“存的位置是挨着的,要说不同的话,也是可视范围吧,全局静态变量仅在当前文件内可见,全局变量是该项目所有文件可见。”

联合(union) 

问:“知道联合吗?” 答:“union” 问:“和结构体有何不同?” 答:“联合的每个成员的拥有共同的起始地址(共享存储空间),而结构体为每一个成员单独分配空间。” 问:“union这样设计的目的是什么(union有何用途)?” 下面我就赶快头脑风暴了一下。。绞尽脑汁地的表达自己的拙见。该部分内容你可以无视,我觉得自己扯得也有点远。。         “这样设计节省内存空间,有时候在某个特定的情况下,我们只需要用的某种特定的类型,如何像结构体那样则浪费了存储空间。在以前的时候Linux编程(POSIX)中IP地址的结构体(struct in_addr)就是一个联合(也可能是结构体成员是联合),比如成员是4个元素char数组,两个元素的short数组,或一个int等等,这样我们就能依据不同的网络类型(A类、B类、C类)来自由的获取该地址的网络号或主机号(比如,要获得一个网络的网络号。若是一个A类地址,我们就读取char数组第一个元素。B类地址我们就读取short的第一个元素来)” 当然了现在的struct in_addr 里面实际上只是包含一个整型的结构体了。不是联合了。上面关于in_addr和联合的说法是从《UNP》上看来的。。

算法

大数相加的算法

问:“如何实现两个数的相加(超过了long long这些的范围了)?” 答:“用一个字符数组来存储数字,然后依次遍历每个字符,通过减‘0’字符的方法转换为数字,再逐位相加。。。” 这是比较经典的大数算法。但他其实没等我说完就打断我了 问:“这样当然可以,但是这种方法效率很低,有没有高效的方法” 答:“不会了” 问:“再想半分钟” 答:“真的不会了(对自己也是无语,求网友告知算法)”

其他算法

问:“你还了解哪些算法” 答:“大部分是学数据结构涉及到的算法,BFS,DFS,最小生成树,最短路径等等。hash也算一种算法吧,还有排序算法。其他的比如像并查集这种数据结构也算吧。” 关于算法我没敢多提,因为我也怕他深入地问下去,好久没搞算法了,这次没准备,肯定会跪。 不过他也没深入的问下去

书籍

问:“你没有项目经验,那你读过什么经典书籍吗?” 答:“C++ primer,Think in C++也读过一点。(其实读过一点的经典书籍还有很多。。)” const指针 问:“声明一个常量指针,指向一个整型,但指向的地址不可变” 哎,这个我知道是重点,也是容易混淆的知识点,前几天我还特地整理了一下。不过,给我点时间我自己慢慢梳理一下可以答好的,他这一问,我才发现我还是掌握不牢固。 答错了。他又指导了我一下。 正确的答案是:const int * a(int const * a)。 int * const a 是指向的整型的值不可变,指针本身可变。 -------------------------------------------------------------------------------------------------------------------------------------------------------------- 总结一下,速记方法:关键的是const与星号(*)的位置。int永远在星号左边的。记成“反转”就行了。可以忽略到int。那么就只有两种形式 const * a和* const a。表面上const * a const在星号前面应该是修饰指针的,但是要反转记忆一下,它是修饰变量的。即变量是常量。 * const a表面上,cosnt在a前面应该是修饰变量的,实际上它是修饰指针的,即地址是常量不能变。
以上仅仅是速记的方法,并不是C语言设计者的设计意图。。。 --------------------------------------------------------------------------------------------------------------------------------------------------------------

内存对齐

问:“比如你malloc了一段内存,它的地址不是内存对齐的,如何实现8字节的内存对齐?” 答:“一个预处理的那个#pragma可以实现(#pragma pack(8))” 问:“这是用编译器来实现,有没有软件方式?” 接下来是在他的提示下,我大概猜测了一下回答的。 答:“先判断malloc的内存地址是不是内存对齐的” 问:“如何判断?” 答:“8字节对齐,那么内存地址应该是8的倍数,可以%8(对8求余)” 问:“这会涉及到除法运算,效率比较低。” 答:“那就用位操作,可以按位与,前面几位是0后面三位是1,哦,我说的是二进制(十进制7)。然后判断值是否为0” 问:“如果结果是没有对齐,该如何对齐呢?” 接下来就完全是我的臆测了 答:“那就给这个地址指针加一下,差多少就加多少,可能还要依据指针类型进行一些转换。”(答的不好。不过他也没提反对意见,就下一题了
--------------------------------------------------------------------------------------------------------------------------------------------------------------
后来我自己手动敲了一下代码,需要注意的问题是指针是不能直接进行求余或位操作的,进行指针到int类型的强制类型转换是失败的。可选方案如下: 如果是C++的话,使用reinterpret_cast [cpp] view plaincopy
  1. long pp = reinterpret_cast<long>(p); // p 是char *类型  

如果pp是int型(reinterpret(p))则会报错提示丢失精度(gcc 64位)。 二面的时候面试官又问了同样的问题,不过问的细节更多,他说可以用static_cast<>来转换指针为整型。我后来试了一下发现不可以。。会报错的。所以我尝试了reinterpret_cast<> 如果是C++的话,就: [cpp] view plaincopy
  1. int pp = reinterpret_cast<int>(p); // p是char *类型  
C语言虽然没这个功能,但其实要想比较指针地址是否是8的倍数,实现还是比较简单的,指针类型是无法指针做&操作的。但是我们可以进行一个小转换: [cpp] view plaincopy
  1. //a 是malloc的返回值,char *类型  
  2. if ((a - (char *)0) & 7)  
  3. {  
  4. ....  
  5. }  
当然这段代码C++也可以用。 要注意的是malloc的返回值最好要强制类型转换为 char *: [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. // 比如分配一百的个字符的空间。  
  2. char *p = (char *)malloc(sizeof(char)*(100+8)); // 多分配8个字节的空间,为了以后的偏移留足空间。  
虽然理论上malloc的返回值可以转化为任意指针类型比如:int *。但是要注意到指针的加减操作,所偏移的单位是指向类型的大小。比如: [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. // 如果p是int *类型  
  2. p += 1; // p向后偏移1*4个字节(int是4个字节)  
  3. // 如果p是char *类型  
  4. p += 1; // p向后偏移1*1个字节(char是1个字节)  

很明显char *类型的指针偏移的精确度更高。这也是为什么我们通常把malloc返回值转换为char *而不是int *的原因。 --------------------------------------------------------------------------------------------------------------------------------------------------------------

回调函数

问:“C++中如何实现回调函数” 回调函数,挺熟的名字,callback。。。但是具体是个什么意思还真不好说。记得在安卓里面见到过。就扯了一下安卓。。 问:“那么在C++中该如何实现呢” 接下来,确实也是运气。脑袋里冒出个函数指针,就脱口而出了,说了个一般的函数指针用法。貌似说对了。 答:“函数指针吧,先什么一种类型的函数的函数指针,然后你可以自己去实现这种类型的函数,然后再把这个函数作为参数传递给函数中(参数是函数指针的函数)。”

内存分配原理

问:“有没有看过内存分配管理的源码?比如malloc之类的。” 答:“没有啊,那大概是汇编吧”(记得大概是Linus说过早期的malloc是用汇编实现的。现在就不知道了。。) 问:“也不是涉及具体语言,就是内存管理的算法了解吗?” 答:“没看过这方面的不了解。。” 然后问题就结束了。现在想想他的意思大概是要我从操作系统的知识方面谈一下内存管理的算法,比如扫描一下,哪里未使用的空间就分配出去之类的。
后来问我有什么问题。我基本没啥问题。问了点弱智问题。 问:“是内推的你们会打电话过来(在某群里找了个内推。。)还是所有在官方申请实习的,你们都会打电话过来?” 答:“一般所有申请的都我们会打过去。”
--------- 后来第二天打来第二个电话,二面。。不过二面挂了