Day13_C语言基础(C语言考试试卷)
上海通立 (嵌入式软件工程师)
上海通立 (嵌入式软件工程师)
一、选择题(每题3分)
1.表达式 0xAB & ~(1<<3) 的结果是:
A. 0xA3
B. 0xAB
C. 0xA8
D. 0xBB答案:A
1010 1011
1111 01111010 0011
-------------------------------------------------------------------------------------------------------------------------
2.以下关于volatile的描述错误的是:
A. 防止编译器优化
B. 保证变量内存可见性
C. 替代const修饰符
D. 常用于硬件寄存器访问答案:C
-------------------------------------------------------------------------------------------------------------------------
3. 32位系统中,sizeof(struct { char a; int b; short c; })的值可能是:
A. 7 B. 8 C. 12 D. 9答案:C
-------------------------------------------------------------------------------------------------------------------------
4. 以下代码的输出是:int i = 5; printf("%d %d", i++, ++i);
A. 5,7 B. 6,6 C. 未定义行为 D. 5,6
答案:C
解析:
规定:一个序列号之前只能修改一次
序列号:;&& l 逗号运算
printf("%d %d", i++, ++i);; 不合法:行为未定义 运行顺序不定-------------------------------------------------------------------------------------------------------------------------
5. 关于malloc和calloc,错误的是:
A. malloc不初始化内存
B. calloc初始化为0
C. malloc(100)分配100元素
D. calloc(10,10)分配100字节答案:C
-------------------------------------------------------------------------------------------------------------------------
二、简述题(每题5分)
1.解释内存对齐原则及其在嵌入式系统中的重要性
答案:
01.结构体的总字节是最宽成员的倍数
02.结构体的总字节大小等于所有成员的字节总和
03.结构体各个成员的偏移量是各个成员字节的倍数
04.结构体的首地址是最宽成员的倍数
字节对齐的重要性,方便数据存储和访问。
-------------------------------------------------------------------------------------------------------------------------
2.Static全局变量与普通全局变量的区别;Static局部变量与普通局部变量的区别;static函数与普通函数区别
Static全局变量不能跨文件调用,其他的和普通全局变量没有区别,Static局部变量可以延长生命周期至本文结束的,作用域还是函数内部,普通局部变量在函数调用结束后立即释放的,作用域也是函数内部,static函数不能跨文件调用,普通函数可以。
答案:
-------------------------------------------------------------------------------------------------------------------------
3.说明const int* p与int* const p的区别
答案:const int*p:*在const右边,修饰值,值不可变,地址可变;
int*const p:*在const左边,修饰地址,值可变,地址不可变
-------------------------------------------------------------------------------------------------------------------------
4.为何嵌入式代码常用typedef重定义数据类型?答案:typedef可以简化数据类型,方便编写代码和调用
typedef struct student{char name[128];int age;char sex;char major[128];float high; }a; //a是struct student 的别名
char *strcat(char *dest, const char *src);typedef char *(*p)(char *, const char *)=strcat;
-------------------------------------------------------------------------------------------------------------------------
5、以下为嵌入式系统下的32位c程序,请计算sheof的值。char strll="Hello" char*p=str; Int n=10 ;
请计算 sizeof (str)= 6 sizeof(p)= 4 sizeof(n)=4
void Func(char str[100]){
sizeof(str)=4//数组也是地址,也是指针的
}
void *p = malloc(100 );
请计算sizeof(p)=4//
答案:sizeof (str)= 6 sizeof(p)= 4 sizeof(n)=4
sizeof(str)=4//数组也是地址,也是指针的
请计算sizeof(p)=4//指针指向malloc申请的内存,本质还是指针
-------------------------------------------------------------------------------------------------------------------------
6.描述内存分配方式以及它们的区别?
答案:
栈区和堆区的区别:
01:栈区满足栈的思想(先进后出),变量内存申请从高地址到低地址申请的
02:堆区满足队列的思想(先进先出),变量内存申请从低地址到高地址申请的
03:栈区的内存8M,堆区的内存2G-3G
04:栈区基本没有碎片化,堆区的内存碎片化严重
05:栈区速度快(寄存器偏移),堆区(操作和指针相关)速度慢
06:栈出现的问题:栈溢出(例如递归操作,函数嵌套太深,内存不足)
07:堆区出行的问题:内存泄漏 0x10---0x90 free(0x50)---堆区的内存未及时释放
08:一般程序函数实在栈区运行的
虚拟内存的划分:
01:32位操作系统为例,默认的虚拟内存大小是4G,地址范围为:0---2^32-1【0G---4G-1】 64位操作系统,虚拟内存2^64,但是该值结果很大,一般用不了,取前48位表示大小,虚拟内存2^48【2^64=256TB】
02:0G---3G:用户空间;3G---4G-1:内核 程序员可以控制的空间就是用户空间 用户空间分为:栈区、堆区、静态区 栈区:计算机自动申请和释放,参数const修饰的局部变量 堆区:程序员手动申请和释放,malloc/calloc/realloc/free 静态区:.bss:存储未初始化的全局和静态变量 .data:存储已经初始化的全局或静态变量 .ro 只读 字符串常量 const修饰的全局变量 .text 文本段,二进制代码,只读
会出现的问题:
01.栈溢出(例如递归操作,函数嵌套太深,内存不足)
02.内存泄漏 0x10---0x90 free(0x50)---堆区的内存未及时释放
03.防止进程之前相互受影响(虚拟内存)
-------------------------------------------------------------------------------------------------------------------------
7.char* s="AAA";
printf("%s", s);
s[o]=’B':
printf("%s”,s):
有什么错?
答案:char *s指针指向字符串常量的首地址,字符串常量在虚拟内存的静态区的.ro只读段,只读段不能被修改,故结果会显示段错误,左值不能被修改,error:不可修改只读段的内容。
-------------------------------------------------------------------------------------------------------------------------
8.解读代码
void GetMemory (char **p, int num){
*p = (char *)malloc(num);}
void Test (void){
Char *str = NULL;
GetMemory (&str, 100);
strcpy (str, "hello");
printf (str);}
请问运行 Test 函数会有什么样的结果?
答案:运行 Test 函数会输出字符串 "hello"。不过,该代码存在多个问题,若不处理这些问题,可能会引发内存泄漏、未定义行为等严重后果。
存在的问题
1. 内存分配检查缺失
在 GetMemory 函数中,使用 malloc 进行内存分配时,没有检查内存分配是否成功。若 malloc 分配内存失败,会返回 NULL,后续对 NULL 指针进行操作会引发未定义行为。
2. 内存泄漏
在 Test 函数中,使用 malloc 分配的内存,在使用完之后没有调用 free 函数释放内存,这会造成内存泄漏。
3. printf 使用不当
printf 函数通常应使用格式字符串 " %s" 来输出字符串,直接使用字符串变量作为 printf 的参数,若字符串包含格式控制字符,可能会导致安全漏洞(如格式字符串攻击)。修改后的代码:
#include <stdio.h> #include <stdlib.h> #include <string.h> // 分配内存的函数 void GetMemory (char **p, int num) {// 分配内存*p = (char *)malloc(num);// 检查内存分配是否成功if (*p == NULL) {fprintf(stderr, "内存分配失败\n");exit(EXIT_FAILURE);} } // 测试函数 void Test (void) {// 修正变量类型声明char *str = NULL;// 调用函数分配内存GetMemory (&str, 100);// 复制字符串到分配的内存strcpy (str, "hello");// 使用正确的格式字符串输出printf("%s\n", str);// 释放分配的内存,避免内存泄漏free(str);str = NULL; } int main() {// 调用测试函数Test();return 0; }
-------------------------------------------------------------------------------------------------------------------------
void Test (void){
char *str = (char *)malloc(100);
strcpy (str, "hello");
free (str);
if (str!=NULL)
{
strcpy (Str, "world");
printf (str);
}}
请问运行 Test 函数会有什么样的结果?
答案:运行 Test 函数可能会产生未定义行为,这可能表现为程序崩溃、输出异常字符,或者看似正常运行但存在潜在风险。段错误
存在错误:
1. 悬空指针问题
在代码里调用 free(str) 释放了 str 指向的内存后,str 就变成了悬空指针。悬空指针指向的内存已经被释放,不再属于当前程序可合法使用的内存区域。后续对悬空指针进行 strcpy 操作,会尝试往已释放的内存写入数据,这是未定义行为。
2. printf 使用不当
直接将字符串指针 str 作为 printf 的参数是不安全的。如果字符串中包含格式控制字符(如 %d、%s 等),printf 会尝试解析这些字符,可能引发格式字符串漏洞。应该使用printf("%s", str) 这种标准形式。
3. 未检查内存分配是否成功
在调用 malloc 分配内存时,没有检查其返回值是否为 NULL。如果内存分配失败,malloc 会返回 NULL,后续对 NULL 指针进行 strcpy 操作同样会导致未定义行为。#include <stdio.h> #include <stdlib.h> #include <string.h> void Test (void){// 分配内存并检查是否成功char *str = (char *)malloc(100);if (str == NULL) {fprintf(stderr, "内存分配失败\n");return;}// 复制字符串到分配的内存strcpy (str, "hello");printf("%s\n", str);// 释放内存并将指针置为 NULLfree (str);str = NULL;// 再次分配内存并检查是否成功str = (char *)malloc(100);if (str == NULL) {fprintf(stderr, "内存分配失败\n");return;}// 复制新字符串到分配的内存strcpy (str, "world");printf("%s\n", str);// 释放内存并将指针置为 NULLfree (str);str = NULL; } int main() {Test();return 0; }
-------------------------------------------------------------------------------------------------------------------------
char *GetMemory (Void){
char p[] = "hello world";
return p;}
void Test (void){
char *str = NULL;
str = GetMemory ()
printf (str)}
请问运行 Test 函数会有什么样的结果?
答案:运行
Test
函数可能会输出乱码,或者导致程序崩溃,这属于未定义行为。段错误存在错误:
1. 局部数组生命周期问题
在 GetMemory 函数里,char p[] = "hello world"; 定义了一个局部字符数组 p。局部变量的内存是在栈上分配的,当 GetMemory 函数执行结束,其栈帧会被销毁,局部数组 p 所占用的内存空间也会被释放。此时返回 p 的地址,得到的是一个悬空指针,该指针指向的内存已经不再有效,后续对这个悬空指针进行操作会引发未定义行为。
2. printf 使用不当
Test 函数中直接使用 printf(str) 存在安全风险。若 str 字符串包含格式控制字符(如 %d、%s 等),printf 会尝试解析这些字符,可能引发格式字符串漏洞,正确做法是使用 printf("%s", str)。#include <stdio.h> #include <stdlib.h> #include <string.h> // 修正参数类型声明 char *GetMemory (void) {// 使用动态内存分配,内存位于堆上,函数返回后不会自动释放char *p = (char *)malloc(strlen("hello world") + 1);if (p == NULL) {fprintf(stderr, "内存分配失败\n");return NULL;}// 复制字符串到分配的内存strcpy(p, "hello world");return p; } void Test (void) {char *str = NULL;// 调用函数获取内存地址str = GetMemory();if (str != NULL) {// 使用正确的 printf 格式输出字符串printf("%s\n", str);// 释放动态分配的内存,避免内存泄漏free(str);str = NULL;} } int main() {Test();return 0; }
-------------------------------------------------------------------------------------------------------------------------
void GetMemory (char *p){
p = (char *)malloc(100);}
void Test (void){
vhar *str = NULL;
GetMemory (str);
strcpy (str, "Hello world");
printf (str);}
请问运行 Test 函数会有什么样的结果?
答案:运行
Test
函数会产生未定义行为,大概率会导致程序崩溃。在某些情况下,程序可能会看似正常运行,但这只是巧合,实际存在严重的安全隐患。段错误。存在的问题
1. 指针传递问题
在 GetMemory 函数里,参数 p 是按值传递的。在 C 语言中,函数参数传递是值传递,也就是说,GetMemory 函数接收到的 p 是 str 的一个副本,对 p 的修改不会影响到 Test 函数中的 str。在 GetMemory 函数中执行 p = (char *)malloc(100); 只是让 p 指向了新分配的内存,而 Test 函数中的 str 仍然为 NULL。
2. 内存分配检查缺失
在 GetMemory 函数中,使用 malloc 进行内存分配时,没有检查内存分配是否成功。若 malloc 分配内存失败,会返回 NULL,后续对 NULL 指针进行操作会引发未定义行为。
3. 内存泄漏
由于 Test 函数中的 str 没有指向 GetMemory 函数中分配的内存,GetMemory 函数分配的内存无法被 Test 函数访问和释放,从而造成内存泄漏。
4. printf 使用不当
printf 函数通常应使用格式字符串 "%s" 来输出字符串,直接使用字符串变量作为 printf 的参数,若字符串包含格式控制字符,可能会导致安全漏洞(如格式字符串攻击)。#include <stdio.h> #include <stdlib.h> #include <string.h> // 使用二级指针作为参数,以便修改调用者的指针 void GetMemory(char **p) {// 分配内存*p = (char *)malloc(100);// 检查内存分配是否成功if (*p == NULL) {fprintf(stderr, "内存分配失败\n");exit(EXIT_FAILURE);} } void Test(void) {// 修正变量类型声明char *str = NULL;// 传递 str 的地址GetMemory(&str);// 复制字符串到分配的内存strcpy(str, "Hello world");// 使用正确的格式字符串输出printf("%s\n", str);// 释放分配的内存,避免内存泄漏free(str);str = NULL; } int main() {// 调用测试函数Test();return 0; }
-------------------------------------------------------------------------------------------------------------------------
三、编程题
1、编写一个函数,作用是把一个char组成的字符串循环右移n个。比如原来是"abcdefghi”如果n=2
,移位后应该是“hiabcdefg”
函数头是这样的:
//pStr是指向以''结尾的字符串的指针
//steps是要求移动的 n
void LoopMove (char * pStr, int steps )(
//请填充...
#include <stdio.h>#include <string.h>#include <stdlib.h>void fun(char *p,int n){int len=strlen(p);char a[128]="";for(int i=0;i<n;i++){a[i]=p[i];}for(int i=len-1;i>=n;i--){p[(i+n)%len]=p[i];}for(int i=0;i<n;i++){p[(i+n)%len]=a[i];}}int main(int argc, const char *argv[]){ char str[]="abcdefghi";int n=2;fun(str,n);puts(str);return 0; }
#include <stdio.h> #include <string.h> #include <stdlib.h> void Loopmove(char* pStr,int steps); #define N 128 int main(int argc, const char *argv[]) {char str[N]="abcdefghi";char *p=str;int len=2;Loopmove(str,len);puts(str);return 0; } void Loopmove(char* pStr,int steps) { int len=strlen(pStr);steps=steps%len;if(steps==0)return;char temp[N];// 先复制字符串的后 steps 个字符到临时数组strcpy(temp, pStr + len - steps);// 再将字符串前面的 len - steps 个字符追加到临时数组strncat(temp, pStr, len - steps);// 把临时数组的内容复制回原字符串strcpy(pStr, temp); }
2、编写 strcpy 函数(12分)
已知 strcpy 函数的原型是 char*strcpy(char *strDest,const char*strSrc);
其中 strDest 是目的字符串,strSrc 是源字符串。
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <stddef.h> void remove_newline(char *str); #define N 128 char* my_strcpy(char* strDest,char* strSrc);int main(int argc, const char *argv[]) { char s1[N];char s2[N];printf("请输入第一个字符串:\n");fgets(s1,N,stdin);remove_newline(s1);printf("请输入第二个字符串:\n");fgets(s2,N,stdin);remove_newline(s2);char *ret=my_strcpy(s1,s2);printf("拷贝后的字符串为:%s\n",ret);return 0; } void remove_newline(char *str) { size_t len=strlen(str);if(len>0&&str[len-1]=='\n'){str[len-1]='\0';} } char* my_strcpy(char* strDest,char* strSrc) { int i;for(i=0;strSrc[i]!='\0';++i){*(strDest+i)=*(strSrc+i);} *(strDest+i)='\0';return strDest; }
3、请选择一种排序说明其逻辑,并编程实现
冒泡排序
#include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc, const char *argv[]) {//当数组初始化后,数组长度可以省略不用写//默认数组长度是实际元素的个数 // int arr[5]={21,3,2,4,6};int arr[]={21,3,2,4,6};//如何计算数组长度//总字节:sizeof(arr)//一个元素字节大小:sizeof(int) sizeof(arr[0])int len=sizeof(arr)/sizeof(arr[0]);int i,j;for(i=1;i<len;++i)//循环轮数,n个元素需要n-1轮即可{for(j=0;j<len-i;++j)//每一轮循环比较的次数{//如果升序:>//如果降序:<if(arr[j] >arr[j+1]){//交换int t=arr[j];arr[j]=arr[j+1];arr[j+1]=t;}}}//循环输出for(i=0;i<len;i++){printf("%-5d",arr[i]);}return 0; }
选择排序
#include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc, const char *argv[]) {int arr[]={23,24,5,2,54};//简单选择排序://默认第一个元素是最值,和后面的元素比较大小,设升序//从后面找最小值,拿最小值第一个元素交换,重复以上过程//知道序列为有序序列int min,i,j;int len=sizeof(arr)/sizeof(arr[0]);for(i=0;i<len-1;++i) //循环轮数,n个元素比较n-1轮即可{//计算最小值min=i; //默认min表示最小值的下表for(j=i+1;j<len;++j) //循环最小值,后面的所有下表{if(arr[min]> arr[j])//比较大小,则保留最小值对应的下表{min=j;}}//i和min下表对应的值实现交换int t=arr[i];arr[i]=arr[min];arr[min]=t;}//循环输出数组for(i=0;i<len;++i)printf("%-4d",arr[i]);return 0; }