面试总结2

面试总结

最近去面了一次试,感觉过程中,发挥的不是很好,持续三个小时,说真的,脑袋有点懵。不过面试官确实很nice,中途不断的给我一些提示,给面试官点一个大赞。

我主要总结几个自己感觉有点转脑筋的问题,我面试的是 linux c/c++,但是因为一直接触和使用的大部分时候都是 c,所以对 c 更熟悉一些,c++ 有些细节方面确实不是很好,这方面需要加强学习。

1、 记得笔试试卷中有一道笔试题

int a = (int)((int*)0 + 4);

第一眼看上去有点懵,这是考的指针,其实你可以把数组联想起来就能明白,分析一下。

(int*)0 是将 0 强制转换成 (int*) 类型,你可以将这看成是地址 0,然后加上 4,这里就需要注意,地址加上 4,不是简单的数值加上 4,如果你把这个地址想象成数组首地址,比如

int arr[2] = {1, 2};
int *p1 = arr;
// 那么 *(p1+1) 的值应该是 2,加上 1 是说的地址加1,表示偏移到了数组的下一个元素

这里的 (int*)0 就相当于指针 p1,加上 4 ,表示的是地址加 4,得到的结果应该是偏移了 4 个 int 整型数的地址 0x10h,所以结果应该是 16。

2、 有一道笔试题中,计算到了 union 和 struct 大小的问题,这个问题,题目中可能有些简单,就是说字节对齐的方式有点太过明显了,我改一下,如下所示:

	typedef union DATA {
		int a;
		char k[5];
		int b;
	}DATA;

	struct NODE {
		int c;
		DATA d;
		char e;
	};
计算 sizeof(struct NODE) + sizeof (DATA) 的值。 首先,看下联合体 DATA,大家都知道,联合体中的各成员变量都是共享一个存储空间的,联合体的大小就是所占空间最大的那个成员的大小。在 DATA 中,字节最大的成员类型是 int,也就是说,需要按照 4 字节对齐,虽然 k[5] 所占空间是 5,但是它的类型是 char,对齐的话,只能算是 1 个字节,所以这里按照 4 字节对齐。 DATA 的大小是最大的成员 k[5],再按照 4 字节对齐,就是 8。

在看结构体 NODE,结构体的大小,是所有成员大小的和,而且还需要考虑字节对齐的问题。NODE 中类型大小最大为 int,按照 4 字节对齐,DATA 的大小为 8,那么 NODE 的大小为 4 + 8 + 4 = 16。最后一个元素 e ,只占一个字节,但是需要按照 4 字节对齐,分配 4 个字节存储。

最后的结果为 16 + 8 = 24

3、 库函数与系统调用的区别
这其实是一道经常出现的笔试题了,为什么放在这里说,是因为网上的答案有很多(确实有很多),但是可能有些都不够全面,我在 《c 专家编程》 一书的附录中找到了一个比较全面的解释。

函数库调用 系统调用
在所有 ANSI C 编译版本中,C 函数库都是相同的 不同操作系统系统调用都不相同
它调用函数库中的一个程序 它调用系统内核中的服务
于用户程序相联系 是操作系统的一个进入点
在用户地址空间执行 在内核地址空间执行
属于过程调用,开销较小 需要在切换到内核上下文环境然后切换回来,开销较大
在 c 函数库中 libc 大约有 300 多个程序 在 UNIX 中大约有 90 多个系统调用
记录于 Man Page 手册页的第三节 记录于 Man Page 手册页的第二节
典型的 c 函数库调用有: system, fprintf, malloc 典型的系统调用: chdir, fork, write, brk

4、 下面一道智力题,把我折腾的够呛,这种题目确实是没有准备,想到了那个点,但是没有最终的解决办法,在面试官耐心的提示下,才搞明白。[捂脸][捂脸]

现有 1000 瓶水,其中只有 1 瓶有毒,现在有 10 只小白鼠,白鼠喝了毒水之后,在 24 小时之后,才会发作,可以看成是死亡。请你在 1 天之内找出该瓶有毒的水。 当时脑子里第一反应,就是 10 只老鼠,能表示 1024 种状态,但是这之间的对应关系不知道怎么建立,就被我直接 PASS 掉了。

我当时的思路:
如果将水分成两组,四组,或者 10 组,那么一天之后,最多只能分辨出哪一组中包含这瓶毒水。这种方法可行,但是白鼠数目不够,PASS。

在面试官的提示下,才弄懂答案。下面我来解释一下:
如果我们将这 1000 瓶水进行编号,分别为 0-1023,对白鼠编号,分别为 0-9,那我们建立这样的对应关系:
  对于编号为 0 的水,不给白鼠喝,编号为 1 的水,让编号为 0 的白鼠喝,编号为 2 的水给编号为 1 的白鼠喝,编号为 3 的水,给编号为 0 和 1 的白鼠喝……差不多到这里就能看出来,这之间就是对应的二进制的关系,按照水的编号,比如编号为 3 ,二进制位 (0b11),那么就分配给第 0 和第 1 编号的白鼠,假设白鼠编号对应二进制的位,编号 9 表示最高位。
如下图所示:
1000瓶水的问题

对结果进行分析:
a. 如果没有白鼠死亡,那么说明给白鼠喝的水是没有毒的,从上述分配的方法可知,编号为 0 的水是没有给白鼠喝的,所以能够断定这瓶水就是毒水;
b. 如果只有编号为 0 的白鼠死亡,那么可以推算,编号为 1 的水有毒。因为编号为 1 的谁只给了 0 号白鼠喝,其他的水如 3 好水给了 0 号 和 1 号白鼠喝,同样还有其他编号的二进制位的最低位为 1 的 (即 0-999 中奇数) 水都给了 0 号白鼠喝,但是其他白鼠都没有死亡,只有 0 号白鼠死亡,能够断定只有 1 号水有毒。

同理,可推算其他的。也就是说,从 0-9 号老鼠最终的状态上,就能够断定哪一瓶水是毒水,直接按照 0-9 所构成的二进制就能获知。

5、 C++ 中等号运算符重载
C++ 中,任何一个类,如果没有用户自定义的赋值运算符函数,那么系统将自动生成一个默认的赋值运算符函数,但是默认的赋值运算符函数仅仅是作“浅拷贝”。 这样容易发生的问题就是,浅拷贝仅仅是拷贝一个地址,即将两个变量同时指向一段内存空间,当其中一个变量释放时,调用析构函数,将空间释放掉,另一个变量就成了__野指针__,再次访问就会出错。

解决这个问题的办法就是等号运算符重载。比如 String 的等号运算符重载。

	String & string::operator= (const string & other)
	{
		// 防止自拷贝发生,这个判断非常重要
		if (this == &other)
			return *this;

		delete m_data;

		m_data = new char[strlen(other.m_data) + 1];
		if (NULL == m_data)
			return NULL;

		strncpy (m_data, other.m_data, strlen(other.m_data));
		return *this;
	}

其中最开始的那段判断很重要,防止发生自拷贝。

等号运算符重载中使用引用,这里不会调用拷贝构造函数,因为引用不占内存,还是指向原来的那段空间,也相当于原对象的一个别名,这样,就少调用了一次拷贝构造函数,能够提高性能。

6、 电话面试中,面试官还问到了“malloc的实现原理,使用了哪些系统调用”?

这个问题,大家可以在网上找找,我推荐两个博客,写的很清楚,也可以自己对照区看源代码。

1. malloc的原理和内存碎片
2. 如何实现一个 malloc