上海欣诺通信技术股份有限公司
软件面试题(总分:100,面试时间:60分钟)一、选择题(14题,每题2分,共28分)
姓名:
1)若有说明int (* p )[3]:以下叙述正确的是()A : p 是指针数组
B :(* p )[3]和* p [3]等价
C : p 是指向一维数组中任何一个元素的指针
D : p 是指向含有3个整型元素的一维数组的指针答案:D
-------------------------------------------------------------------------------------------------------------------------
2)以下可以用于任务间通信的有()
A :共享内存
B :信号量
C :消息队列和管道
D : socket 调用答案:ACD
解析:
选项A:共享内存
共享内存是一种高效的任务间通信方式。多个任务(进程或线程)可以访问同一块物理内存区域,任务可以直接在这块内存上
读写数据,避免了数据的复制,从而提高了通信效率。例如在 Linux 系统中,进程可以通过 shmget、shmat 等系统调用创建
和挂载共享内存区域,实现进程间的数据共享。所以共享内存可用于任务间通信。选项B:信号量
信号量主要用于任务间的同步和互斥,而非直接的通信。它是一个计数器,用于控制多个任务对共享资源的访问。通过 P 操作(申请资源)和 V 操作(释放资源)来保证同一时间只有一个任务可以访问共享资源,防止数据竞争和不一致问题。虽然信号量可以间接传达任务的状态信息(如资源是否可用),但它本身不用于传输具体的数据,所以信号量不能用于任务间通信。选项C:消息队列和管道
消息队列:消息队列是一种消息的链表,存放在内核中并由消息队列标识符标识。任务可以向消息队列中发送消息,也可以从消息队列中读取消息,实现了不同任务间的数据传递。例如在 Linux 系统中,进程可以使用 msgget、msgsnd 和 msgrcv 等系统调用进行消息队列的操作。
管道:管道是一种半双工的通信方式,数据只能单向流动。有匿名管道和命名管道两种类型。匿名管道主要用于具有亲缘关系的进程间通信,命名管道则可以在任意两个进程间通信。例如在 Linux 命令行中,ls | grep test 就是使用管道将 ls 命令的输出传递给 grep 命令进行过滤。所以消息队列和管道可用于任务间通信。选项D:socket 调用
Socket 即套接字,是一种网络编程接口,可用于不同主机上的任务间通信,也可用于同一主机上的任务间通信。通过创建不同类型的 Socket(如 TCP Socket、UDP Socket),任务可以建立连接、发送和接收数据,实现跨网络或本地的通信。例如,服务器和客户端程序可以通过 Socket 进行数据交互。所以 Socket 调用可用于任务间通信。-------------------------------------------------------------------------------------------------------------------------
3)正确的使用 assert ,软件系统不会对人为原因造成的错误进行处理。它有利于()
A :缺陷及早暴露,加快定位进程;
B :明确表示函数接口或算法逻辑的校验关系,增强软件代码的可读性;
C :去除不必要的容错处理,简化代码处理流程,降低软件处理复杂度;
D :强化编程接口之间的契约,增强软件的可维护性;答案:ABCD
解析:
选项A
assert 断言主要用于在程序开发和调试阶段检查程序中的假设条件是否成立。如果断言条件为假,程序会立即终止并输出错误信息,这样可以让开发人员及时发现程序中的逻辑错误,使得缺陷能够及早暴露。同时,错误信息会指出断言失败的位置,有助于开发人员快速定位问题所在,加快调试进程。所以该选项正确。选项B
在代码中合理使用 assert 可以明确表达函数接口的前置条件、后置条件以及算法逻辑中的关键假设。例如,在函数开始处使用assert 检查传入参数是否符合要求,这能让其他阅读代码的人清晰地了解函数的使用规则和逻辑校验关系,增强了软件代码的可读性。所以该选项正确。选项C
assert 主要用于检测程序中的编程错误,这些错误通常是不应该出现的,比如空指针引用、数组越界等。对于这类错误,在开发阶段使用 assert 可以快速定位问题,而不需要在代码中添加大量复杂的容错处理逻辑。这样可以简化代码处理流程,降低软件处理复杂度。不过需要注意的是,在发布版本中,通常会禁用 assert,所以它适用于开发和调试阶段去除不必要的容错处理。所以该选项正确。选项D
assert 可以强化编程接口之间的契约。通过在函数接口处添加断言,能够明确规定函数调用者需要满足的条件(前置条件)以及函数执行后应满足的条件(后置条件)。当函数调用违反这些契约时,assert 会触发错误,这有助于在开发过程中维护接口的正确性,使得后续开发人员能够清楚地了解接口的使用规则,增强软件的可维护性。所以该选项正确。-------------------------------------------------------------------------------------------------------------------------
4)对于循环体的执行效率,下述说法正确的是()
A :循环体内工作量最小化
B :在多重循环中,应将最忙的循环放在最内层
C :尽量减少循环的嵌套层次
D :避免在循环体内做无谓的判断语句,将循环语句置于判读语句的代码块之中答案:ACD
-------------------------------------------------------------------------------------------------------------------------
5)设数组 a [5]=(10,20,30,40,50]:已知指针 p 指向 a [1];则表达式*++ p 的值是()
A .20 B .21 C .30 D .31
答案:C
-------------------------------------------------------------------------------------------------------------------------
6)有以下程序段,执行后, mul 的值为()
int a []=(1,3,5,7,9);
int mul ,* data , x ;
mul =1;
data =& a [1];
for ( x =0; x <3; x ++)
mul *=*( data + x );
A .15
B .105
C .315
D .945答案:B
-------------------------------------------------------------------------------------------------------------------------
7)有以下程序段,执行后的结果为()
int list []=[6,7,8,9,10);
int * p ; p = list ;
*( p +2)=10;
printf ("% d ,% d \ n ",* p ,*( p +2));
A .8,10
B .6,8
C .7,9
D .6,10答案:D
-------------------------------------------------------------------------------------------------------------------------
8)以下语句不能正确赋值的是()
A .char s1[10];s1=" China ";
B . char s2[]=(' C ',' h ',' r ' n ',' a ' };
C .char s3[20]=" China ";
D .char *s ="China";答案:A
-------------------------------------------------------------------------------------------------------------------------
9)C语言中,定义PI为一个符号常量,正确的有()
A .# define Pl 3.14
B .define Pl 3.14
C. #include Pr 3:14
D . include P13.14答案:A
-------------------------------------------------------------------------------------------------------------------------
10)者以下程序,读程序输出的结果为()
# define N 3
# define M N +2
# define NUM 2* M +1
main (){
int i ,
i = NUM :
printf ("% d \ n ",i);
}
A .11 B .9 C .7 D .5答案:B
-------------------------------------------------------------------------------------------------------------------------
11)有以下程序,程序运行后的输出结果是()
# include < string . h >
# include < stdio . h >
main ()
{ char * p =" abcde \ 0fghjik \ 0 ";
printf ("% d \ n ", strlen ( p ));}
A .12 B .15 C .6 D .5答案:D
-------------------------------------------------------------------------------------------------------------------------
12)为了比较两个字符串s1和s2是否相等,应当使用()
A . if (s1=s2)
B . if (s1==s2)
C . if ( strcmp (s1,s2)==0)
D . if ( strcmp (&s1,&s2)==0)答案:C
-------------------------------------------------------------------------------------------------------------------------
13)设有如下结构定义:
struct student
{ int num ;
char name [20];
char sex ;
int age ;
char addr [30];
} stud ;
若用 printf ("% s \ n ",…)访问该结构中 name 值的正确方法是()
A . stud -> name
B .& stud . name
C . stud .& name
D . stud . name答案:D
解析:
选项A:stud -> name 在 C 语言中,-> 是指针访问结构体成员的运算符。当使用指针指向结构体时,通过 -> 来访问结构体的成员。而 stud 是结构体变量,不是指针,不能使用 -> 运算符,所以该选项错误。
选项B:&stud.name & 是取地址运算符,&stud.name 表示取 stud 结构体中 name 数组的首地址,而 printf("%s\n", ...) 期望的参数是一个字符串(即字符数组的首地址,以 \0 结尾),直接传入地址的地址不符合要求,不能正确输出 name 的值,所以该选项错误。
选项C:stud.&name 在 C 语言中,没有 stud.&name 这样的语法,& 是取地址运算符,不能这样和结构体成员访问运算符 . 组合使用,所以该选项错误。
选项D:stud.name . 是结构体变量访问成员的运算符。对于结构体变量 stud,可以使用 . 来访问其成员。stud.name 表示访问 stud 结构体中的 name 成员,name 是一个字符数组,在 C 语言中,数组名会隐式转换为数组首元素的地址,这正是printf("%s\n", ...) 所需要的参数类型,能正确输出 name 字符串的值,所以该选项正确。-------------------------------------------------------------------------------------------------------------------------
14)想对一个文本文件的尾部追加,应在 fopen 语句中使用的文件操作方式指示符号为()
A . w
B . r
C . wb
D . a答案:D
解析:
选项A:w
在 fopen 函数中,w 表示以写入模式打开文件。若文件存在,会将文件内容清空;若文件不存在,则创建一个新文件。所以w 模式并非用于在文件尾部追加内容,该选项错误。选项B:r表示以只读模式打开文件。使用此模式打开文件时,只能从文件中读取数据,不能向文件写入数据,更不能在文件尾部追加内容,该选项错误。
选项C:wb
wb 是以二进制写入模式打开文件。和 w 模式类似,若文件存在会清空内容,若文件不存在则创建新文件,只是以二进制形式操作文件。因此,wb 模式也不是用于在文件尾部追加内容,该选项错误。选项D:a
a 表示以追加模式打开文件。若文件存在,会将文件指针定位到文件末尾,后续写入的数据会追加到文件原有内容之后;若文件不存在,则创建一个新文件。所以 a 模式符合在文本文件尾部追加内容的需求,该选项正确。-------------------------------------------------------------------------------------------------------------------------
二、填空题(6题,每题5分,共30分)
1)如下代码,最终 value 的值是()
int *p1,*p2;
int value ;
p1=( int *)0x400;
p2=( int *)0x408;
value =p2-p1;答案:2
-------------------------------------------------------------------------------------------------------------------------
2)如下代码, printf 的结果为()
# include ( stdio . h )
# include ( string . h )
void main ( void ){ char acNew [20]="\\0\0";
printf ("% d \ n ", strien ( acNew ));}
答案:2
-------------------------------------------------------------------------------------------------------------------------
3)有如下程序段,运行该程序的输出结果是()
main ()
( int y =3, x =3, z =1;
printf ("%d%d\n ",(++ x , y ++),z+2);}答案:33
-------------------------------------------------------------------------------------------------------------------------
4)设有: int a =1, b =2,c=3, d =4,m=2, n =2:执行( m = a > b )&&( n = c > d )后, n 的值为()
答案:2
解析:&&运算符的短路特性
-------------------------------------------------------------------------------------------------------------------------
5) struct tagAAA
{
unsigned char a :1;
unsigned char :7;unsigned char b :2;
unsigned char c :6;
unsigned char d :4;
unsigned char e ;
unsigned char f :4;
unsigned long g ;
) AAA _ s ;问:在32位机器上 AAA _ S 在字节对齐分别为1,4情况下,占用的空间大小分别是多少?
()()答案:10 12
解析:
位域相关知识
位域是一种特殊的结构体成员,它允许在一个字节中指定成员所占的二进制位数,从而节省存储空间。在计算位域占用空间时,不同的编译器可能有不同的实现,一般遵循以下规则:
01.当相邻位域成员的类型相同时,如果它们的位宽之和小于类型的大小,则它们会共用同一个存储单元;如果超过类型的大小,则会从下一个存储单元开始存放。
02.当相邻位域成员的类型不同时,不同编译器的处理方式可能不同,一般会按类型的大小进行对齐。字节对齐相关知识
字节对齐是指数据在内存中存储时,按照一定的规则进行排列,以提高内存访问效率。不同的字节对齐方式会影响结构体占用的空间大小。常见的字节对齐规则如下:
01.结构体的首地址必须是其最大基本类型成员大小的整数倍。
02.结构体每个成员的偏移量必须是该成员大小的整数倍。
03.结构体的总大小必须是其最大基本类型成员大小的整数倍。在字节对齐为 1 的情况下,结构体成员会依次紧密排列,不会进行额外的填充。(是否是位域?)
unsigned char a : 1;a 占 1 位。
unsigned char : 7;这是一个 7 位的无名位域,与 a 共用一个 unsigned char 类型的存储单元,所以这部分共占 1 字节。
unsigned char b : 2; 和 unsigned char c : 6;:b 占 2 位,c 占 6 位,它们共用一个 unsigned char 类型的存储单元,共占 1 字节。
unsigned char d : 4;d 占 4 位,由于后续没有能与之共用的位域,所以单独占 1 字节。
unsigned char e;:e 占 1 字节。
unsigned char f : 4;f 占 4 位,单独占 1 字节。
unsigned long g;:在 32 位机器上,unsigned long 占 4 字节。
将各部分占用的字节数相加,可得 1 + 1 + 1 + 1 + 1 + 1 + 4 = 10 字节。在字节对齐为 4 的情况下,需要考虑成员的偏移量和结构体的总大小。
unsigned char a : 1; 和 unsigned char : 7;共占 1 字节。
unsigned char b : 2; 和 unsigned char c : 6;共占 1 字节。
unsigned char d : 4;占 1 字节。
unsigned char e;占 1 字节。这 4 个 unsigned char 类型的成员共占 4 字节,刚好满足字节对齐要求。
unsigned char f : 4;占 1 字节,由于要满足下一个成员 unsigned long 4 字节对齐的要求,需要填充 3 字节,使其偏移量为 4 的整数倍。
unsigned long g;:在 32 位机器上占 4 字节。
将各部分占用的字节数相加,可得 4 + 1 + 3 + 4 = 12 字节。-------------------------------------------------------------------------------------------------------------------------
6)
# include < stdio . h >
main ()
{ int x , y =0;
for ( x =1; x <=10; x ++)
{if ( y >=10)
break ;
y = y + x ;
}
printf ("% d % d ", y , x );
}问:输出结果多少()
答案:10 5
-------------------------------------------------------------------------------------------------------------------------
三、指出下列程序的错误(5题,每题4分,共20分)
1).如下程序用于输出" Welcome Home "。请指出其中的错误:(4分)word void Test ( void )
{
char pcArray [12];
strcpy ( pcArray ," Welcome Home ");
printf ("% s !", pcArray );return ;}
答案:char pcArray[12]字符数组的长度小于拷贝过来的字符串的长度" Welcome Home ",忽略了字符串中的'\0'字符,需要把char pcArray[12]长度修改为大于等于13的长度即可。
-------------------------------------------------------------------------------------------------------------------------
2).如下程序用于把" blue "字符串返回,请指出其中的错误:(4分)char * GetBLUE ( void ){
char * pcColor ;char * pcNewColor ;
pcColor =" blue ";
pcNewColor =( char *) malloc (strlen ( pColor ));
if ( NULL == pcNewColor )
{
return NULL ;}
strcpy ( pcNewColor , pcColor );
return pcNewColor;}答案:
存在错误:
1. 变量名拼写错误
pcNewColor = (char *)malloc(strlen(pColor)); 这行代码里,pColor 变量名拼写有误,正确的变量名是 pcColor。2. 内存分配空间不足
strlen 函数返回的字符串长度不包含字符串结束符 '\0',但 strcpy 函数在复制字符串时会将结束符也复制过去。所以使用 malloc 分配内存时,需要多分配一个字节来存放 '\0'。修改后的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdlib.h>
#include <string.h>char *GetBLUE(void) {
char *pcColor;
char *pcNewColor;pcColor = "blue";
// 修正变量名,多分配一个字节存放字符串结束符
pcNewColor = (char *)malloc(strlen(pcColor) + 1);
if (NULL == pcNewColor) {
return NULL;
}
strcpy(pcNewColor, pcColor);
return pcNewColor;
}-------------------------------------------------------------------------------------------------------------------------
3).下面程序期望输出 str = hello world ,请指出其中的错误:(4分)char * GetStr ( char * p )
{
p =" hello world ";
return p ;}
void main ()
{
char * str = NULL ;
if ( NULL != GetStr ( str ))
{
printf ("\ r \ n str =% s ", str );}
return ;
}答案:
存在错误:
1. main 函数返回类型问题
在 C 语言标准中,main 函数的推荐返回类型为 int,而非 void。main 函数返回一个整数给操作系统,用于表示程序的退出状态,通常 0 表示正常退出。
2. 指针传递问题
在 GetStr 函数中,参数 p 是按值传递的。这意味着在 GetStr 函数内部对 p 进行赋值操作,只会改变函数内部 p 这个局部变量的指向,而不会影响 main 函数中 str 指针的指向。
3. 函数返回值使用问题
在 main 函数里,调用 GetStr(str) 后,没有将返回值赋给 str 指针,所以 str 仍然为 NULL。
4. 头文件缺失
代码中使用了 printf 函数,需要包含 <stdio.h> 头文件。修改后的代码1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
// 定义 GetStr 函数,返回一个指向字符串的指针
char * GetStr() {
// 直接返回字符串常量的指针
return "hello world";
}int main() {
char * str = NULL;
// 将 GetStr 函数的返回值赋给 str 指针
str = GetStr();
if (NULL != str) {
// 输出字符串
printf("\n str = %s ", str);
}
return 0;
}修改后的代码2(使用二级指针):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
// 定义 GetStr 函数,通过二级指针修改一级指针的指向
void GetStr(char ** p) {
// 修改一级指针的指向
*p = "hello world";
}int main() {
char * str = NULL;
// 传递 str 的地址给 GetStr 函数
GetStr(&str);
if (NULL != str) {
// 输出字符串
printf("\n str = %s ", str);
}
return 0;
}-------------------------------------------------------------------------------------------------------------------------
4).请指出下面程序错误的地方:(4分)# define BUFFER _ SIZE 256
void Test ( void )
{ char * str = NULL ;
str =( char *) malloc ( BUFFER _ SIZE );
if ( NULL == str )
{return ;
}
strcpy ( str ," hello ");
free ( str );
if ( NULL != str )
{
strcpy ( str ," world ");
printf ( str );}
return ;}
答案:
存在错误:
1. 宏定义中的空格问题
# define BUFFER _ SIZE 256 存在问题,宏定义中不应该有多余的空格,正确写法是 #define BUFFER_SIZE 256。
2. 内存释放后使用指针
free(str) 这行代码将 str 指向的内存空间释放了。释放后的内存空间不再受程序管理,可能会被系统重新分配给其他程序使用。之后再判断 if (NULL != str) 并执行 strcpy(str, "world"); 和 printf(str); 属于典型的悬空指针(野指针)使用问题,会导致未定义行为,可能引发程序崩溃或者数据损坏。
3. printf 函数使用风险
printf(str); 这种使用方式存在安全风险,因为 str 包含用户提供或者动态生成的内容时,可能会遭受格式字符串攻击。建议使用 printf("%s", str); 这种明确指定格式的方式。
4. 缺少头文件
代码中使用了 malloc、strcpy、free 和 printf 函数,需要包含相应的头文件。malloc 和 free 函数在 <stdlib.h> 中声明,strcpy 函数在 <string.h> 中声明,printf 函数在 <stdio.h> 中声明。修正后代码1:
修正后代码2:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
#include <stdlib.h>
#include <string.h>// 定义缓冲区大小的宏
#define BUFFER_SIZE 256// 定义测试函数
void Test(void) {
char *str = NULL;
// 分配内存
str = (char *)malloc(BUFFER_SIZE);
if (NULL == str) {
return;
}
// 复制字符串到分配的内存
strcpy(str, "hello");
// 打印字符串
printf("%s\n", str);
// 释放内存
free(str);
// 避免悬空指针,将指针置为 NULL
str = NULL;str=(char *)malloc(BUFFER_SIZE);
if(NULL==str){
return;
}
// 复制字符串到分配的内存
strcpy(str, "world");
// 打印字符串
printf("%s\n", str);
// 释放内存
free(str);
// 避免悬空指针,将指针置为 NULL
str = NULL;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
#include <stdlib.h>
#include <string.h>// 定义缓冲区大小的宏
#define BUFFER_SIZE 256// 封装重复操作的函数
void process_string(const char *src) {
// 分配内存,去掉类型转换
char *str = malloc(BUFFER_SIZE);
if (str == NULL) {
// 输出错误信息
perror("Memory allocation failed");
return;
}
// 复制字符串到分配的内存
strcpy(str, src);
// 打印字符串
printf("%s\n", str);
// 释放内存
free(str);
}// 定义测试函数
void Test(void) {
process_string("hello");
process_string("world");
}int main() {
Test();
return 0;
}-------------------------------------------------------------------------------------------------------------------------
5).请指出下面程序错误的地方:(4分)void GetMemory ( char ** ppcChar , int iLength )
{
if ( NULL == ppcChar )
{return ;
}
* ppcChar =( char *) malloc ( iLength );
return ;}
void Test ( void ){
char * szStr = NULL ;
GetMemory (& szStr ,100);
if ( NULL != szStr )
{ strcpy ( szStr ," hello ");
printf ( szStr );
} return ;}
答案:
存在错误:
1. 头文件缺失
代码中使用了 malloc、strcpy 和 printf 函数,却没有包含对应的头文件。malloc 函数定义在 <stdlib.h> 头文件中,strcpy函数定义在 <string.h> 头文件中,printf 函数定义在 <stdio.h> 头文件中。
2. 内存分配空间不足
GetMemory 函数分配内存时,没有考虑字符串结束符 '\0' 的空间。strcpy 函数会复制字符串结束符,所以分配的内存空间需要比字符串长度多 1 字节。
3. printf 函数使用风险
printf(szStr); 这种使用方式存在安全风险,当 szStr 包含用户输入或者动态生成的内容时,可能会遭受格式字符串攻击。建议使用 printf("%s", szStr); 这种明确指定格式的方式。
4. 内存泄漏问题
代码中分配了内存,但没有释放,会导致内存泄漏。在使用完动态分配的内存后,需要调用 free 函数释放内存。
5. 内存分配失败处理不完善
GetMemory 函数在内存分配失败时,没有给调用者提供明确的错误信息,也没有*ppcChar 进行处理。修正后代码1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <stdio.h>
#include <stdlib.h>
#include <string.h>// 定义 GetMemory 函数,用于分配内存
void GetMemory(char **ppcChar, int iLength) {
if (NULL == ppcChar) {
return;
}
// 多分配 1 字节用于存储字符串结束符'\0'
*ppcChar = (char *)malloc(iLength + 1);
if (*ppcChar == NULL) {
// 输出错误信息
perror("Memory allocation failed");
}
return;
}// 定义 Test 函数进行测试
void Test(void) {
char *szStr = NULL;
// 调用 GetMemory 函数分配内存
GetMemory(&szStr, 100);
if (NULL != szStr) {
// 复制字符串到分配的内存
strcpy(szStr, "hello");
// 安全地打印字符串
printf("%s\n", szStr);
// 释放分配的内存
free(szStr);
// 避免悬空指针,将指针置为 NULL
szStr = NULL;
}
return;
}int main() {
Test();
return 0;
}-------------------------------------------------------------------------------------------------------------------------
四、解答题(2题,共7分)
1)写宏定义 MAX ,求出两个数中的较大者(3分)。
1 #define MAX(a,b) ((a)>(b)?(a):(b))
#define MAX a>b?a:b
2)实现 strcpy 函数(4分)
#include <stdio.h> #include <string.h> #include <stdlib.h> char* my_strcpy(char *p,char*q,int len); #define N 128 int main(int argc, const char *argv[]) { char dest[N]={};char source[N]={};printf("请输入要拷贝的字符串:\n");fgets(source,N,stdin);size_t len=strlen(source);//手动去除fgets读取的换行符if(len>0&&source[len-1]=='\n'){source[len-1]='\0';}char* result=my_strcpy(dest,source,len);printf("打印拷贝后的字符串:%s\n",result);return 0; } char* my_strcpy(char *p,char*q,int len) { for(size_t i=0;i<len;++i){*(p+i)=*(q+i); }*(p+len)='\0';return p; }