文章目录
- 前言
- 一、数据类型
- 1、用变量 a 给出下面的定义
- 2、下面的代码输出是什么,为什么?
- 3、写出 float x 与“零值”比较的 if 语句。
- 4、下面代码有什么错误?
- 5、下面代码输出是什么?
- 6、下面代码运行后会是什么现象?
- 7、下面函数的返回值是?
- 8、结构体内存对齐原则?
- 9、结构体内存对齐的原因?
- 10、给定的位域结构体,它在内存中占用多少字节(32位编译器)?
- 11、在32位系统中,有如下结构体,那么sizeof(fun)的数值是?
- 12、数组首元素地址和数组地址的异同?
- 13、下面代码输出是什么?
- 14、判断下列表达式正确与否?
- 15、查看下面代码,p[6] 等于几?
- 16、下面代码的输出结果是什么?
- 17、变长数组是什么?
- 18、bool 类型包含于哪个头文件?
- 19、结构体struct和联合体union的区别?
- 20、给了一个地址a,分别强转类型为:int变量、int指针、数组指针、指针数组、函数指针。
- 21、执行完下面代码,c 的值是多少?
- 22、C语言中不同数据类型之间的赋值规则?
前言
记录一些招聘公司在招聘嵌入式软件岗位时的一些问题,此文为第三篇。
一、数据类型
1、用变量 a 给出下面的定义
- 一个整型数: int a。
- 一个指向整型数的指针(一重指针): int *a。
- 一个指向指针的的指针,它指向的指针是指向一个整型数的指针(二重指针): int **a。
- 一个有10个整型数的数组 :int a[10]。
- 一个有10个指针的数组,这10个指针是指向整型数的(指针数组): int *a[10]。
- 由于 [] 的优先级高于 *,所以首先是一个数组,然后数组的元素是指向int的指针。
- 一个指向有10个整型数数组的指针(数组指针):int (*a)[10]。
- 由于括号 () 的优先级高于 [],所以首先是一个指针,然后这个指针指向一个包含10个整数的数组。
- 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(函数指针):int (*a)(int)。
- 一个有10个指针的数组,这10个指针均指向函数,该函数有一个整型参数并返回一个整型数(函数指针数组): int (*a[10])(int)。
2、下面的代码输出是什么,为什么?
void foo(void)
{
unsigned int a = 6;
int b = -20;
(a + b > 6)? printf("> 6") : printf(" <= 6");
}
答:输出是 “>6”。
解读:当运算表达式中存在有符号数和无符号数时,有符号数隐式转换成了无符号数(即底层的补码不变,但是此数从有符号数变成了无符号数)。注意,正数的补码为其本身,负数的补码为其反码+1。因此-20变成了一个非常大的正整数,所以该表达式计算出的结果 ”>6”。
3、写出 float x 与“零值”比较的 if 语句。
答:
if(x > -0.000001 && x < 0.000001);
解读:因为计算机在处理浮点数的时候是有误差的,所以不能将浮点型变量用 “==” 或 “!=” 与数字比较,应该设法转化成 “>” 或 “<” 此类形式。
4、下面代码有什么错误?
#include<stdio.h>
void main()
{
char *s = "AAA";
s[0] = 'B';
printf("%s", s);
}
- "AAA"是字符串常量,s 是指针,指向这个字符串常量,所以声明 s 的时候就有问题,应该是 const char* s=“AAA”。
- 然后又因为是常量,所以对是 s[0] 的赋值操作是不合法的。
5、下面代码输出是什么?
#include<stdio.h>
void main()
{
int *a = (int *)2;
printf("%d", a + 3);
}
答:输出是14。
解读:代码将数值 2 强制类型转换为 int 类型指针,int 类型指针加 3 相当于指向后面第三个 int 类型变量的首地址,一个 int 类型变量占 4 个字节,所以加 3 相当于指针往后移了 12 个字节,指向地址 14 处。
6、下面代码运行后会是什么现象?
#include<stdio.h>
#define N 500
void main()
{
unsigned char count;
for(count = 0; count < N; count++)
{
printf("---%d---\n", count);
}
}
答:进入不断打印 count 值的死循环。
解读:因为 unsigned char 类型变量的最大值为 255,所以 count 只能从 0 一直增加到 255,然后又恢复为 0,无法退出 for 循环。
7、下面函数的返回值是?
int foo(void)
{
int i;
char c = 0x80;
i = c;
if(i > 0)
return 1;
return 2;
}
答:返回值为2。
解读:因为 0x80 == 128,超出了char类型变量c的表示范围(-128~127),所以c == -128,进而 i == -128,i < 0。
8、结构体内存对齐原则?
答:
- 第一个成员的首地址(地址偏移量)为0。
- 成员对齐:以 4 字节对齐为例,如果自身类型小于 4 字节,则该成员的首地址是自身类型大小的整数倍;如果自身类型大于等于 4 字节,则该成员的首地址是 4 的整数倍。若内嵌结构体,则内嵌结构体的首地址也要对齐,只不过自身类型大小用内嵌结构体的最大成员类型大小来表示。数组可以拆开看做 n 个数组元素,不用整体看作一个类型。
- 最后结构体总体补齐:以 4 字节对齐为例,如果结构体中最大成员类型小于 4 字节,则大小补齐为结构体中最大成员类型大小的整数倍;如果大于等于 4 字节,则大小补齐为 4 的整数倍。内嵌结构体也要补齐。
- 示例:
-
示例 1:基本对齐
#include <stdio.h> typedef struct { char a; // 1字节,对齐到1字节 int b; // 4字节,对齐到4字节 } SimpleStruct; int main() { printf("Size of SimpleStruct: %lu\n", sizeof(SimpleStruct)); // 预期输出是8,因为`char a`后面会填充3字节,以确保`int b`对齐到4字节。 return 0; }
-
示例 2:内嵌结构体
#include <stdio.h> typedef struct { double d; // 8字节,对齐到8字节 } InnerStruct; typedef struct { char c; // 1字节,对齐到1字节 InnerStruct s; // 内嵌结构体,对齐到8字节 } OuterStruct; int main() { printf("Size of OuterStruct: %lu\n", sizeof(OuterStruct)); // 预期输出是16,因为`char c`后面会填充7字节以确保`InnerStruct s`对齐到8字节。 return 0; }
-
示例 3:数组对齐
#include <stdio.h> typedef struct { char x; // 1字节,对齐到1字节 int arr[2]; // 8字节,数组的每个元素对齐到4字节 } ArrayStruct; int main() { printf("Size of ArrayStruct: %lu\n", sizeof(ArrayStruct)); // 预期输出是12,因为`char x`后面填充3字节以确保数组`arr`的首元素对齐到4字节。 return 0; }
-
示例 4:结构体补齐
#include <stdio.h> typedef struct { char x; // 1字节,对齐到1字节 short y; // 2字节,对齐到2字节 } PaddedStruct; int main() { printf("Size of PaddedStruct: %lu\n", sizeof(PaddedStruct)); // 预期输出是4,因为总体大小补齐到2的整数倍。 return 0; }
-
注意:32 位编译器,一般默认对齐方式是 4 字节。
9、结构体内存对齐的原因?
- 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据。
- 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐,因为访问未对齐的内存,处理器需要做两次内存访问,而访问对齐的内存仅需要一次。如下图所示,访问对齐的short变量只需要一次,而访问未对齐的int变量则需要访问两次。
10、给定的位域结构体,它在内存中占用多少字节(32位编译器)?
struct A
{
char t : 4; // 4位
char k : 4; // 4位
unsigned short i : 8; // 8位
unsigned long m; // 4字节
};
根据结构体内存对齐原则,共占用 8 字节。
11、在32位系统中,有如下结构体,那么sizeof(fun)的数值是?
#pragma pack(1)
struct fun
{
int i; // 4字节
double d; // 8字节
char c; // 1字节
};
答:sizeof(fun)
得到的结果是 13。
解读:因为预处理语句 ”#prama pack(1)
” 将编译器的字节对齐数改为 1 了,根据结构体内存对齐原则,该结构体占用的字节数为 13。
12、数组首元素地址和数组地址的异同?
- 异:数组首元素地址和数组地址是两个不同的概念。例如 int a[10],a 的值是数组首元素地址,所以 a+1 就是第二个元素的地址,int 类型占用 4 个字节,所以两者相差 4。而 &a 是数组地址,所以 &a+1就是向后移动(10*4)个单位,所以两者相差 40。
- 同:数组首元素地址和数组地址的值是相等的。
13、下面代码输出是什么?
#include <stdio.h>
void main()
{
int a[5] = {1, 2, 3, 4, 5};
int *ptr = (int *)(&a + 1);
printf("%d, %d", *(a + 1), *(ptr - 1));
}
答:输出为 2, 5。
解读: a是数组首元素地址,所以 *(a + 1)就是第二个元素 a[1]。&a是数组地址,所以&a + 1是整个数组结尾的下一个地址,*(ptr - 1)就是a[4]。
14、判断下列表达式正确与否?
char str[2][3] = {“a”, “b”}; // 正确,str是一个可存放两个字符串的字符串数组
char str[2][3] = {{1, 2}, {3, 4}, {5, 6}}; // 错误,行列不匹配
char str[] = {“a”, “b”}; // 错误,字符数组不能存放两个字符串
char str[2] = {“a”, “b”}; // 错误,字符数组不能存放两个字符串
答:以注释形式展示。
注意:在 C 语言中字符用 ’ ’
括起来,而字符串用 ” ”
括起来。
15、查看下面代码,p[6] 等于几?
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
int *p = &a[1];
答:等于8。
解读:p 是一个int类型指针,指向 a[1],p[6] 表示 p 往后移了6个单位(每个单位4个字节)并解引用,因此 p[6] 等于8。
16、下面代码的输出结果是什么?
#include <stdio.h>
void main()
{
char *str[] = {"ab", "cd", "ef", "gh", "ij", "kl"}; //指针数组
char *t;
t = (str + 4)[-1];
printf("%s", t);
}
答:输出 “gh”。
解读:str 表示数组首元素地址,str + 4 表示数组第五个元素地址,(str + 4)[-1] 表示在第五个元素地址的基础上往前移一个元素并解引用,因此输出是第四个元素。
17、变长数组是什么?
在 C99 标准中,允许定义数组时 [] 中的值是整型变量或整型表达式,比如说下面的代码在支持 C99 标准的编译器中可编译通过:
#include <stdio.h>
void main()
{
int n;
scanf(“%d”, &n);
int array[n];
}
18、bool 类型包含于哪个头文件?
在 C99 标准中位于 stdbool.h。
注意:C89 标准中则不支持,需要自己定义,代码如下:
#define TRUE 1
#define FALSE 0
typedef int bool;
bool res = TRUE;
19、结构体struct和联合体union的区别?
- 两者最大的区别在于内存的使用。
- 结构体各成员拥有自己的内存,各自使用且互不干涉,遵循内存对齐原则。
- 联合体所有成员共用一块内存空间,并且同时只有一个成员可以得到这块内存的使用权。一个联合体变量的总长度应至少能容纳最大的成员变量,且需要进行内存补齐。
20、给了一个地址a,分别强转类型为:int变量、int指针、数组指针、指针数组、函数指针。
21、执行完下面代码,c 的值是多少?
unsigned int a = 1;
int b = 0;
int c = 0;
c = a + b > 0 ? 1 : 2;
答:c 的值是1。
解读:有符号数与无符号数一起运算时,有符号数转为无符号数。有符号数int b = 0的在内存中的补码是0,因此转为无符号数之后依然是0,a + b == 1 > 0,c == 1。
22、C语言中不同数据类型之间的赋值规则?
- 整数与整数之间(char, short, int, long):
- 长度相等:内存中的数据不变,只是按不同的编码格式来解析。
- 长赋值给短:截取低位,然后按短整数的数据类型解析。
- 短赋值给长:如果都是无符号数,短整数高位补0;如果都是有符号数,短整数高位补符号数;如果一个有符号一个无符号,那么先将短整数进行位数扩展,过程中保持数据不变,然后按照长整数的数据类型解析数据。
- 整数与浮点数之间
- 浮点数转整数:截取整数部分。
- 整数转浮点数:小数部分为0,整数部分与整数相等。
- float 与 double之间
- double 转 float会丢失精度。
- float 转 double不会丢失精度。
注意:整数在内存中都是以补码的形式存储的。
我的qq:2442391036,欢迎交流!