C语言面试题---指针篇(一)

版本声明:本文转载于公众号TeachPlus

指针的使用,一直是c语言面试题中必考的部分,
因为指针本身使用的复杂性与普适性,所以考点非常多,而且也可以与其他知识相互结合,
因此我们将会使用五篇专题的篇幅来介绍指针。分析下面的程序,指出程序中的错误:
# include <stdio.h>
int  main( void)
{
    char a;
    char *str=&a;
    strcpy(str,"hello");
    printf("%s\n",str);
    return 0;
}

本题解析

没有正确为str分配内存空间,将会发生异常。
问题出在将一个字符串复制进一个字符变量指针所指地址。
虽然编译的时候没有报错,但是在运行过程中,因为越界访问了未被分配的内存,而导致段错误。

相关知识点

在处理与指针相关的问题时,首先需要搞明白的就是内存,因为指针操作的就是内存。
第一个,就是内存的分区。这也是经常会被考察的一个考点。
写出内存分为几大区域
对于这个问题,有几种不不同的说法。
有的说内存分为五大分区,有的说分为四大分区,我们先来先看五个分区的说法:

认为内存分为五大分区的人,通常会这样划分:

1、BSS段( bss segment )

通常是指用来存放程序中未初始化的全局变量和静态变量 (这里注意一个问题:一般的
书上都会说全局变量和静态变量是会自动初始化的,那么哪来的未初始化的变量呢?变量的
初始化可以分为显示初始化和隐式初始化,全局变量和静态变量如果程序员自己不初始化的话的确
也会被初始化,那就是不管什么类型都初始化为0,这种没有显示初始化的就是我们这里所说的未初始化。
既然都是0那么就没必要把每个0都存储起来,从而节省磁盘空间,这是BSS的主要作用)的一块内存区域。
BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。 
BSS节不不包含任何数据,只是简单的维护开始和结束的地址,即总大小。
以便内存区能在运行时分配并被有效地清零。BSS节在应用程序的二进制映象文件中并不存在,
即不占用磁盘空间而只在运行的时候占用内存空间 ,所以如果全局变量和静态变量未初始化那么其可执行文件要小很多。

2、数据段(data segment)

通常是指用来存放程序中已经初始化的全局变量和静态变量的一块内存区域。
数据段属于静态内存分配,可以分为只读数据段和读写数据段。
字符串常量等,但一般都是放在只读数据段中。

3、代码段(code segment/text segment)

通常是指用来存放程序执行代码的一块内存区域。
这部分区域的大小在程序运行前就已经确定,
并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。
在代码段中,也有可能包含一些只读的常数变量,例例如字符串常量等,但一般都是放在只读数据段中 。

4、堆(heap)

堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。
当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张); 
当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)

5、栈 (stack)

栈又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}” 中定义
的变量(但不不包括static声明的变量,static意味着在数据段中存放变量)。
除此以外, 在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,
函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以 栈特别方便用来保存/恢复调用现场。
从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。

而四大分区的说法,则这么认为:

1、堆区:

由程序员手动申请,手动释放,若不手动释放,程序结束后由系统回收,生命周期是整个程序运
行期间。使用malloc或者new进行堆的申请,堆的总大小为机器器的虚拟内存的大小。
说明:new操作符本质上是使用了malloc进行内存的申请,new和malloc的区别如下:
(1)malloc是C语言中的函数,而new是C++中的操作符。
(2)malloc申请之后返回的类型是void*,而new返回的指针带有类型。
(3)malloc只负责内存的分配而不会调用类的构造函数,而new不仅会分配内存,
而且会自动调用类的构造函数。

2、栈区:

由系统进行内存的管理理。主要存放函数的参数以及局部变量。
在函数完成执行,系统自行释放栈区内存,不需要用户管理。
整个程序的栈区的大小可以在编译器器中由用户自行设定,
VS中默认的栈区大小为1M,可通过VS手动更改栈的大。
64bits的Linux默认栈大小为10MB,可通过ulimit-s临时修改。

3、静态存储区:

静态存储区内的变量在程序编译阶段已经分配好内存空间并初始化。这块内存在程序的整个运行
期间都存在,它主要存放静态变量、全局变量和常量。
注意:
(1)这里不区分初始化和未初始化的数据区,是因为静态存储区内的变量若不显示初始化,
则编译器会自动以默认的方式进行初始化,
即静态存储区内不存在未初始化的变量。
(2)静态存储区内的常量分为常变量和字符串常量,一经初始化,不可修改。
静态存储内的常变量是全局变量,与局部常变量不不同,
区别在于局部常变量存放于栈,实际可间接通过指针或者
引用进行修改,而全局常变量存放于静态常量区则不可以间接修改。
(3)字符串常量存储在静态存储区的常量区,字符串常量的名称即为它本身,属于常变量。
(4)数据区的具体划分,有利利于我们对于变量类型的理理解。
不同类型的变量存放的区域不同。后面将以实例代码说明这四种数据区中具体对应的变量。

4、代码区:

存放程序体的二进制代码。比如我们写的函数,都是在代码区的。

通过上面的不同说法,我们也可以看出,这两种说法本身没有优劣之分,
具体的内存划分也跟编译器有很大的关系,因此这两种说法都是可以接受的,
搞明白内存的分区之后,指针的使用才能够更更加的灵活

<think>好的,用户想要查找与C语言指针相关的面试题。我需要根据之前提供的引用内容来整理相关题目和解析。首先,看看用户提供的四个引用,都是关于C语言指针面试题示例,应该可以从中提取出几个典型的题目。 首先,引用1中的题目涉及数组和指针的操作,特别是数组名和指针运算的区别。这题的结果是2和5,需要解释清楚数组名和指针的类型转换后的差异。 然后,引用2的结构体指针题目涉及到指针运算和类型转换。结构体的大小是20字节,所以p+0x1的地址应该是0x100000 + 20,也就是0x100014。而(unsigned long)p + 1则是数值上的加1,结果为0x100001。unsigned int*类型的指针加1,则增加4字节,所以是0x100004。这部分需要详细说明指针运算和类型转换的影响。 引用3的题目是关于指针数组和二级指针的操作。pa初始指向数组a的第个元素,pa++后指向第二个元素,即"at",所以输出是at。这个相对简单,但需要解释指针数组和二级指针的递增操作。 引用4中的题目涉及到字符串指针和strlen函数的使用。这里有几个易错点,比如解引用指针后的行为是否正确,取地址后的随机值问题。需要逐分析每个printf的输出结果或错误原因。 接下来,我需要将这些题目整理成用户需要的格式,使用中文,并且确保行内公式和独立公式的正确应用。不过这些题目可能不需要数学公式,但如果有涉及地址计算的地方,可能需要使用行内代码或地址表示,比如使用$...$或代码块。 用户要求生成相关问题,但根据示例,可能是指生成更多相关问题,但根据用户的问题,他们可能希望直接看到已有的题目和解析。不过根据用户提供的示例,他们可能希望将现有的引用内容中的题目整理出来,并附上解析,然后生成相关问题。 现在需要按照用户提供的系统级指令来组织回答,包括使用正确的格式,生成相关问题,引用标识等。同时,回答需要结构清晰,帮助用户逐步解决问题。 首先,整理每个引用中的题目,给出解析,然后生成相关问题。例如,引用1的题目解析,引用2的结构体指针问题,引用3的指针数组问题,引用4的strlen问题。然后针对每个题目生成相关问题,如指针运算、类型转换、结构体内存对齐、二级指针操作等。 需要确保每个解析正确,并正确引用来源。例如,在解析每个题目后,添加对应的引用标识,如[^1]、[^2]等。同时,用户可能需要更多的相关问题,以便他们进步练习,所以需要根据每个题目的知识点提出相关问题。 最后,检查是否符合用户的所有要求,包括中文回答,LaTeX语法正确,行内代码用反引号包裹,引用标识正确添加,结构清晰等。</think>### C语言指针常见面试题解析 #### 1. 数组名与指针运算差异 **题目**: ```c int main() { int a[5] = {1,2,3,4,5}; int* ptr = (int*)(&a + 1); printf("%d %d", *(a + 1), *(ptr - 1)); return 0; } ``` **解析**: - `a`是数组首元素地址,类型为`int*`,`a + 1`指向第二个元素(值为2)。 - `&a`是数组的地址,类型为`int(*)[5]`,`&a + 1`会跳过整个数组(地址增加`5*sizeof(int)`),此时`ptr`指向数组末尾后的位置。 - `ptr - 1`回退个`int`大小,指向数组最后个元素(值为5)。 - **结果**:`2 5`。 #### 2. 结构体指针与类型转换 **题目**: ```c struct Test { /* 结构体大小为20字节 */ }* p = (struct Test*)0x100000; int main() { printf("%p\n", p + 0x1); printf("%p\n", (unsigned long)p + 0x1); printf("%p\n", (unsigned int*)p + 0x1); } ``` **解析**: - `p + 0x1`:指针运算按结构体大小(20字节)增加,结果为`0x100014`。 - `(unsigned long)p + 1`:数值运算直接加1,结果为`0x100001`。 - `(unsigned int*)p + 1`:指针类型为`unsigned int*`,加1增加4字节,结果为`0x100004`。 #### 3. 指针数组与二级指针 **题目**: ```c int main() { char* a[] = {"work","at","alibaba"}; char** pa = a; pa++; printf("%s\n", *pa); return 0; } ``` **解析**: - `a`是字符指针数组,`pa`初始指向`a[0]`(即`"work"`)。 - `pa++`后指向`a[1]`(即`"at"`),`*pa`解引用得到字符串`"at"`。 - **结果**:`at`[^3]。 #### 4. 指针与`strlen`函数陷阱 **题目**(简化): ```c char arr[] = "abcdef"; char* p = arr; // 分析以下表达式的行为: strlen(p); // 6 strlen(p + 1); // 5 strlen(*p); // 错误(类型不匹配) strlen(&p); // 随机值(非字符串地址) ``` **解析**: - `strlen(*p)`:`*p`是字符`'a'`(ASCII 97),将97作为地址访问会导致未定义行为。 - `strlen(&p)`:`&p`是`char**`类型,`strlen`会从指针地址开始读取直到遇到`\0`,结果不可预测[^4]。 --- ###
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值