让人挠头的C语言测试题

前言

题目来自于https://kobes.ca/ctest ,共16题。

题目

#include <stdio.h>
#include <setjmp.h>

static jmp_buf buf;

int main(void)
{
   volatile int b = 3;

   if (setjmp(buf) != 0)
   {
      printf("%d\n", b);
      exit(0);
   }
   b = 5;
   longjmp(buf, 1);
}

解析:理解setjmp与longjmp后,本题很容易解答。首次调用setjmp时,会标记jmp_buf buf并返回0,当调用longjmp后,会跳回setjmp并返回longjmp的第二个参数val(当val为0时,setjmp返回1),所以当配对的longjmp调用后,setjmp返回值永不为0。

答案:5


#include <stdio.h>

int main(void)
{
   struct node
   {
      int a;
      int b;
      int c;
   };
   struct node s = { 3, 5, 6 };
   struct node *pt = &s;

   printf("%d\n", *(int*)pt);

   return 0;
}

解析:这一题很简单,pt为结构体s的指针,将pt强转为int *,而结构体的第一个成员变量a就是int类型,所以此时对(int *)pt取值得到的就是a的值。

答案:3

拓展:如果将题目改成这样

int main(void)
{
      struct node
    {
       char a;
       char b;
    };
    struct node s = { 2, 1 };
    struct node *pt = &s;

    printf("%d\n", *(short*)pt);    // 258
   return 0;
}

由于char占用1byte,short占用2byte,因此刚好可以读取a、b中的值。对于结构体s来说,由于a、b都是char类型,a的二进制数据为00000010b,b的二进制数据为00000001b。在小段模式下,s中存储的二进制数据为00000001_00000010b,转换成十进制为258,所以最终输出258。


int foo(int x, int n)
{
   int val = 1;

   if (n > 0)
   {
      if (n % 2 == 1)
         val *= x;

      val *= foo(x * x, n / 2);
   }
   return val;
}

3

解析:其实就是初中数学题,本题控制变量为n,推导出n分别为奇偶数的表达式即可。

答案:a


#include <stdio.h>

int main(void)
{
   int a[5] = { 1, 2, 3, 4, 5 };
   int *ptr = (int*)(&a + 1);

   printf("%d %d\n", *(a + 1), *(ptr - 1));

   return 0;
}

解析:&a是指向a[5]的指针,此时每移动一个单位指针步长为5,所以&a + 1指向a[5],这是一个未定义的值。将&a + 1赋值给ptr,此时ptr同样指向a[5],由于指针类型为int *,所以ptr - 1指针移动一个步长,此时指向a[4]。而a + 1指向a[1],所以最终输出2和5。

答案:2 5


#include <stdio.h>

void foo(int[][3]);

int main(void)
{
   int a[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };

   foo(a);
   printf("%d\n", a[2][1]);

   return 0;
}

void foo(int b[][3])
{
   ++b;
   b[1][1] = 9;
}

解析:如果第四题理解没有难度,这一题也不难理解。调用foo函数++b后,b指向{4, 5, 6}这个数组,此时的b[1][1]数值为后一个数组{7, 8, 9}中的8,赋值后数组{7, 8, 9}变为{7, 9, 9},所以此时a[2][1]取出的数据为{7, 9, 9}数组的9。

答案:9


#include <stdio.h>

int main(void)
{
   int a, b, c, d;
   a = 3;
   b = 5;
   c = a, b;
   d = (a, b);

   printf("c=%d  ", c);
   printf("d=%d\n", d);

   return 0;
}

解析:考察运算符的优先级,()>=>,
c = a, b先运算=,所以c等于a等于3;
d = (a, b)先运算()内的表达式,a, b的结果为b,所以d等于b等于5

答案:c=3 d=5


#include <stdio.h>

int main(void)
{
   int a[][3] = {1, 2, 3, 4, 5, 6};
   int (*ptr)[3] = a;

   printf("%d %d ", (*ptr)[1], (*ptr)[2]);

   ++ptr;
   printf("%d %d\n", (*ptr)[1], (*ptr)[2]);

   return 0;
}

解析:这是第四题的升级版,ptr为int [3]类型指针并指向a[0],所以此时(*ptr)[1]等价于a[0][1](*ptr)[2]等价于a[0][2]。++ptr后,ptr相当于指向a[1],所以此时(*ptr)[1]等价于a[1][1](*ptr)[2]等价于a[1][2]

答案:2 3 5 6


#include <stdlib.h>

int *f1(void)
{
   int x = 10;
   return &x;
}

int *f2(void)
{
   int *ptr;
   *ptr = 10;
   return ptr;
}

int *f3(void)
{
   int *ptr;
   ptr = malloc(sizeof *ptr);
   return ptr;
}

Which of these functions uses pointers incorrectly?

(a) f3 only
(b) f1 and f3
(c) f1 and f2
(d) f1, f2, and f3

解析:

  1. f1返回局部变量x的地址,而局部变量在函数返回后已销毁,因此返回的指针指向未定义
  2. f2中ptr未初始化,*ptr = 10直接crash
  3. f3正确的为ptr分配了一块内存空间

答案:c


#include <stdio.h>

int main(void)
{
   int i = 3;
   int j;

   j = sizeof(++i + ++i);

   printf("i=%d j=%d\n", i, j);

   return 0;
}

解析:sizeof并不会对括号内的表达式做运算,只是检测表达式的类型,因此sizeof(++i + ++i)等价于sizeof(int),所以j等于4。

答案:i=3 j=4

拓展:sizeof存在类型提升,比如:

int a = 1;
double b = 3.14;
sizeof(a + b);

a为int类型,b为double类型,此时等价于sizeof(double)


#include <stdio.h>

void f1(int*, int);
void f2(int*, int);
void (*p[2])(int*, int);

int main(void)
{
   int a = 3;
   int b = 5;

   p[0] = f1;
   p[1] = f2;

   p[0](&a, b);
   printf("%d %d ", a, b);

   p[1](&a, b);
   printf("%d %d\n", a, b);

   return 0;
}

void f1(int *p, int q)
{
   int tmp = *p;
   *p = q;
   q = tmp;
}

void f2(int *p, int q)
{
   int tmp = *p;
   *p = q;
   q = tmp;
}

解析:考察基本功,典型的值传递与地址传递。f1与f2 ,a为值传递,b为地址传递。所以a可变,b不可变。f1执行后p指向了q,所以a的值等于q等于b等5,q的值改变不会影响b,f2同理。

答案:5 5 5 5

拓展:地址传递的本质仍然是值传递,只不过传递的是地址的值。比如函数f1中的p和q,其实都是局部变量,之所以可以通过p改变a的值,实际是通过*p对p指向的内存地址重新赋值。p本身也是可变的,如果重新对p赋值,此时改变*p并不会对a造成影响:

void f1(int *p) {
    *p += 5;
}

void f2(int *p) {
    int x = 10;
    p = &x;
    *p += 5;
}

int main() {
    int a = 1;
    int b = 1;
    f1(&a);
    f2(&b);
    printf("%d\n", a);  // 6
    printf("%d\n", b);  // 1
    
    return 0;
}

对于C++来说,还存在另一种参数传递方式,引用传递。
两者的区别在于:
地址传递压栈的是指针的副本,通过对副本指针寻址从而改变实参的值;
引用传递是真正的传址,形参实参都指向同一块内存,只是名字不同而已

void f3(int &p) {
    p += 5;
}


int main() {
    int a = 1; 
    f3(a);
    printf("%d\n", a);  // 6
    
    return 0;
}

#include <stdio.h>

void e(int);

int main(void)
{
   int a = 3;
   e(a);

   putchar('\n');
   return 0;
}

void e(int n)
{
   if (n > 0)
   {
      e(--n);
      printf("%d ", n);
      e(--n);
   }
}

解析:考察点是递归调用,调用关系如下:

e(3)->{
    e(2)->{
        e(1)->{
            e(0)->{},
            0,
            e(-1)->{}
        },
        1,
        e(0)->{}
    },
    2,
    e(1)->{
            e(0)->{},
            0,
            e{-1}->{}
    }
}

答案:0 1 2 0


typedef int (*test)(float*, float*);
test tmp;
12

解析:函数指针的定义方式为:

函数返回值类型 (* 指针变量名) (函数参数列表)

test显然是个函数指针,他指向返回值为int,两个参数都为float *的函数。test的类型为int(*)(float *, float *)

答案:c

拓展:函数指针与指针函数
函数指针是个指针,他指向某个函数;
指针函数是个函数,他返回某个指针。


#include <stdio.h>

int main(void)
{
   char p;
   char buf[10] = {1, 2, 3, 4, 5, 6, 9, 8};

   p = (buf + 1)[5];
   printf("%d\n", p);

   return 0;
}

解析:考察C语言的语法,buf[5]等价于buf + 5(buf + 1)[5]等价于buf + 1 + 5,等价于buf + 6,即buf[6]

答案:9


#include <stdio.h>

void f(char**);

int main(void)
{
   char *argv[] = { "ab", "cd", "ef", "gh", "ij", "kl" };
   f(argv);

   return 0;
}

void f(char **p)
{
   char *t;

   t = (p += sizeof(int))[-1];

   printf("%s\n", t);
}

解析:如果十三题理解了,这一题也不难。(p += sizeof(int))[-1]等价于p + sizeof(int) - 1,等价于p + 4 - 1,等价于p + 3,即p[3]

答案:gh


#include <stdarg.h>
#include <stdio.h>

int ripple(int n, ...)
{
   int i, j, k;
   va_list p;

   k = 0;
   j = 1;
   va_start(p, n);

   for (; j < n; ++j)
   {
      i = va_arg(p, int);
      for (; i; i &= i - 1)
         ++k;
   }
   va_end(p);
   return k;
}

int main(void)
{
   printf("%d\n", ripple(3, 5, 7));
   return 0;
}

解析:初始变量n等于3,j等于1,外层for循环每次j自增1,这些条件确保了i刚好可以被可变参数列表中的3和7赋值。而i &= i - 1是在控制内部for循环的循环次数。
下面分别对i等于5和7时,进行i &= i - 1运算i结果:

i

起始值:101b  (5的二进制表达)
第一次运算后的结果:100b
第二次运算后的结果:000b
运算2次后为0

起始值:111b    (7的二进制表达)
第一次运算后的结果:110b
第二次运算后的结果:100b
第三次运算后的结果:000b
运算3次后为0

当i等于0时for循环终止,其实这种for循环写法的循环次数等价于起始值转成二进制中1的个数,而i &= i - 1就是在消除变量二进制最低位的1。

所以内部for循环共循环了5次,k起始值为0,5次++k运算后,k等于5

答案:5


#include <stdio.h>

int counter(int i)
{
   static int count = 0;
   count = count + i;
   return count;
}

int main(void)
{
   int i, j;

   for (i = 0; i <= 5; i++)
      j = counter(i);

   printf("%d\n", j);
   return 0;
}

解析:考察局部静态变量,没啥好说的j = 0 + 1 + 2 + 3 + 4 + 5

答案:15


Have fun!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,884评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,212评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,351评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,412评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,438评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,127评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,714评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,636评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,173评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,264评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,402评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,073评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,763评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,253评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,382评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,749评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,403评论 2 358

推荐阅读更多精彩内容