前些天公司摸底 C 语言考试,得分比较难看,回来发了考试答案,这篇博客把我做错的题目拿出来理一理,补补课。

  • 判断对错:在定义数据结构时,没有特殊理由的话,都定义成四字节对齐;这样做可能浪费几个字节,但是不会出问题。

    这道题答案是对的,需要仔细 Google。

  • 以下程序运行 (64 位系统) 后的输出结果是 6 5 8 5

    int main()
    {
      char str1[] = "Hello";
      char str2[] = {'H', 'e', 'l', 'l', 'o'};
      char *p = str1;
      printf("%d %d %d %d\n", sizeof(str1), sizeof(str2), sizeof(p), strlen(p));
    }
    

    这道题我的答案是 6,5,4,5,原因在于 64 位的指针的长度大小是 8 个字节,而不是一般的 4 个字节。 考点为 字符串数组长度 (+1), 64 位指针长度 (8) 字符串数组 x 包含末尾的’\0’

  • 如下程序, 在 64bit 系统运行输出为 24 4 8 16

    struct s1
    {
        char a;
        int b;
        short c;
        double d;
    };
    int main(void)
    {
        printf("%d %d %d %d\n",
        sizeof(struct s1), offsetof(struct s1, b), offsetof(struct
        s1, c), offsetof(struct s1, d) ) ;
        //注: offsetof 为计算偏移量的宏
        return 0;
    }
    

    我的答案是 48,8,16,32,错误成了 2 倍。主要为结构体对齐规则, 这部分需要强化 一般考生都可以看到 b 按 4 字节对齐,需要填充 忽视 double 也需要 8 字节对齐 double 在 32 位 linux 平台下可能按照 4 字节对齐

  • 如下指针计算, 结果为 4

    int *p1 = (int *)0x500;
    int *p2 = (int *)0x510;
    printf(“%d\n”, (p2 - p1));
    

    我错成了 16,答案应该是 16/4,指针的减法,按照指针类型计算跨越的步长

  • 如下程序, 请问输出多少? -128,128

    void main(void)
    {
    char x = 127;
    char a = x + 1;
    int b = x + 1;
    printf("%d %d", a, b);
    }
    

    我的答案是 128,128,整型加法隐式提升到 int 然后运算,所以 x+1 就是 128 溢出出现在赋值运算,第一个溢出了,第二个没有。没有考虑到第一个溢出之后的数据显示问题

  • 如下程序片段, 输出为: 2 7

    int *p;
    int x[3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    p = x[0] + 1;
    printf("%d %d\n", *p, x[2][0]);
    

    我的答案是 4 7 。x[0] 是对二级指针解引用,类型是指向整型的指针,指向 1,+1 后,指向 x[0][1] 没争议

  • 计算如下定义长度 (64 位系统)

    typedef union {
        int a;
        long b;
        char c[6];
    }un;
    sizeof(un) = 8
    

    我的答案是 48,联合体的内存大小不会算,共用体的占用空间的大小,按照其最大成员算 本题中 long 是 8 字节

  • 请问输出多少

    union packet
    {
        struct packet_bit
        {
            unsigned char a:2;
            unsigned char b:3;
            unsigned char c:4;
        } bit;
        int i;
    } data;
      
    int main()
    {
        data.i = 0;
        data.bit.a = 1;
        data.bit.b = 2;
        data.bit.c = 0xF;
        printf("0x%04x\n", data.i);
    }
    

    我的答案是 0xf,这道题完全是蒙出来的,考察的是位域的概念,位域结构体,按地址从低到高依次存储 a、b、c 一个规则大家可能都不熟悉: 一个位域必须存储在同一个字节中,不能跨两个字节 所以 a 和 b 储存在一个字节,c 再进来存不下,所以 c 单独存放在一个字节 再就是注意输出格式

  • 如下程序, 请写出打印结果 A b

    void fun(char *a, char *b)
    {
        a = b;
        (*a)++;
    }
    void main()
    {
        char c1 = 'A', c2 = 'a';
        char *p1 = &c1;
        char *p2 = &c2;
        fun(p1, p2);
        printf("%c %c\n", c1, c2);
    }
    

    我的结果是 a b, 函数入参是按值传递的,只有通过传递地址才能改变函数外部的值,这里有一个陷阱,使用的地址,实际是从参数 b 传进来的,所以改变的也是参数 b 指向的值 。这道题不该错,因为在 fun 函数里,a 的值已经是 b 了,所以 a 指向的就是 b 指向的地址,因此改变的就是 c2 指向的字母啊

  • 下面函数的输出是 token3 = 4

    #define paster( n ) printf( "token" #n " = %d", token##n )
    void fun()
    {
        int token3 = 4;
        int tokenn = 3;
        paster( 3 );
    }
    

    我的答案是 token3 = 3,这是个知识盲点,可以参考 C 语言宏定义 ## 连接符和 #符的使用,# 将本身的字符替换之后再在两边加上双引号,## 是机械单纯得将两个 token 连接在一起

  • 如下程序, 输出为 100008 100001 100004

    struct test
    {
        int a;
        int b;
    };
    int main ()
    {
        struct test *p = (struct test *)0x100000;
        printf("%x %x %x\n", (p+1), (long)p+1, (int *)p +1);
    }
    

    我的答案是 0x100008,0x100001,0x11。这道题比较有意思,程序第 8 行已经说明了,p 的值就是 0x100000(也就是说 p 指向的地址是 0x100000),后面 (int *)p,说明了它指向的是 int 类型,那么 + 1 就是在原来的地址上一个 int 的字节数,也就是 0x100004。需要总结的是,指针前面的表明的是这个指针指向的数据类型。