用于记录一些比较奇怪/少见的c语法/易错易忘点/常见问题, 以备考试等用途

杂项中的杂项

  • 编译没过先看报错信息, 不认识英文请善用翻译
  • 如何提问: 先使用搜索引擎. 如果一定要问人, 附上全部 全部 全部 代码, 以及报错信息 报错信息 报错信息. 和一组出错了的输入输出, 以及注释或你对代码的解释. 因为阅读别人的代码是一件很烧脑的事.
    • 错误示范: 代码截图, 我错哪了?
    • 不要拍屏
  • 查错技巧: 学习使用gdb, 设置断点并查看相关变量的值. 一个低配但很快的方法是用printf输出关键变量
  • 关于CSDN: 虽然内容质量感人被StackOverflow等英文网站甩了十条街, 但还是有一些值得一看的文章的. 而且哪怕用百度查到csdn文章看了也比啥也不查直接问强.
  • 输入数据之后卡住了怎么办: 查看你的代码里是否有死循环, 或者你的输入格式是否合法.
  • 输入输出对不上: 看看格式控制符跟变量类型能否对应, 常见错误是用%f读入double类型变量.
  • 报错信息里有cannot open output file .../*.exe: permission denied或者"无法打开xxx程序进行写入"之类的东西: 你之前的程序没关掉
  • 输出了奇怪的数字: 大概率数组越界
  • Segmentation fault: 访问了不可访问的内存. 常见原因是数组越界, 指针漂移, 缓冲区溢出, 数组开太大爆栈…
  • 带缺省值的参数需要放在后面, 这样在调用函数的时候就可以不写那个参数. 放在前面编译会报错.
int f(int a = 0, int b); // wrong
int f(int b, int a = 0); // right
  • 字符串占用的空间要算上 ‘\0’

  • 关于锟斤拷烫烫烫等成因: 手持两把锟斤拷,口中疾呼烫烫烫,脚踏千朵屯屯屯,笑看万物锘锘锘

    1. 锟斤拷

    源于GBK字符集和Unicode字符集之间的转换问题。Unicode和老编码体系的转化过程中,肯定有一些字,用Unicode是没法表示的,Unicode官方用了一个占位符来表示这些文字,这就是:U+FFFD REPLACEMENT CHARACTER。那么U+FFFD的UTF-8编码出来,恰好是 ‘\xef\xbf\xbd’。如果这个’\xef\xbf\xbd’,重复多次,例如 ‘\xef\xbf\xbd\xef\xbf\xbd’,然后放到GBK/CP936/GB2312/GB18030的环境中显示的话,一个汉字2个字节,最终的结果就是:锟斤拷——锟(0xEFBF),斤(0xBDEF),拷(0xBFBD)。

    1. 烫烫烫 VS/VC debug模式下栈内未初始化的内存会全部被设置成0xcc, 这个0xcc是INT3中断指令的机器码, 所以执行这块内存就会中断程序. VS调试器默认字符集是MBCS, 其中0xcccc是’烫’字, 所以……

    INT3断点是断点的一种,在诸如Ollydbg中的快捷键是F2,是一种很常用的断点类型。INT3指令的机器码为CC,所以通常也称之为CC指令。当被调试进程执行INT3指令导致一个异常时,调试器就会捕捉这个异常从而停在断点处,然后将断点处的指令恢复成原来的指令。当然,如果自己写调试器,也可以用其他一些指令代替INT3指令来触发异常。

    注: 关于INT3指令的问题可以查阅内中断相关知识, 简单来讲执行INT n指令会调用编号n对应的中断处理程序, 而INT 3是x86系列cpu专门用于调试的指令, 它的中断处理程序会寻找调试器并将控制转交给调试器, 这些知识在汇编语言课里就会学到.
    3. 屯屯屯 由于相似的原因, 堆中空间的缺省值是0xcd, 也是一个中断指令的机器码, 0xcdcd是屯 4. 锘锘锘 BOM 是 Byte Order Mark 的缩写。是UTF编码方案里用于标识编码的标准标记,在UTF-16里本来是FF FE,变成UTF-8就成了EF BB BF。这个标记是可选的,因为UTF8字节没有顺序,所以它可以被用来检测一个字节流是否是UTF-8编码的。 常见原因是在记事本里保存了代码, 因为记事本用utf-8编码, 而且保存自动加BOM 锘EFBB 匡BFEF 豢BBBF 5. 葺葺葺 虽然诗里没有, 但堆上申请后释放空间后缺省值是0xdd, 0xdddd是葺 虽然这个问题在中文环境里令人尤其迷惑, 但英文使用者同样对未初始化的内存为何是这些值感到好奇. When and why will a compiler initialise memory to 0xCD, 0xDD, etc. on malloc/free/new/delete?

  • <优先级比=高, 这使

if(x = f() < 0)

并不会像看起来那样工作, 它会首先执行f()<0, 最后被赋值给x的是这个关系运算符的真(1)假(0).

  • 宏/函数/内联函数
    • 参数: 宏的参数在展开时不经任何处理直接进行字符串替换, 函数的参数会进行类型检查, 计算后由实参传值到形参. 形参是函数内的局部变量, 占用栈空间.
    • 宏的展开由预处理器处理, 在预编译(编译之前)时进行, 直接用宏体替换宏名, 占用预编译时间. 函数在执行时才会被调用. 调用时需要保护现场(保存调用者保存的寄存器), 进入函数, 返回主调函数, 恢复现场(恢复寄存器).
    • 宏会有各种副作用, 这使它难于调试
      • 由于简单的展开, ++ –运算符可能会执行多次
      • 同样由于简单的展开, 如果不加足够多的括号, 会出很多优先级的问题.
    • 但宏也有很多好处
      • 可以在并不直接允许重载的C语言里实现简单的重载(不过用指针等方法也可以实现)
      • 对于简单而重复多次的功能, 可以大大减小开销, 提升速度
      • 实现其他奇技淫巧
    • 内联函数(inline)
      • 由编译器处理
      • 用类似宏的方法, 将函数代码直接嵌入到调用处, 不能有循环, 选择等任何复杂结构, 可以节约开销的同时防止宏的一些副作用.
      • 如果函数不够简单, 编译器会拒绝内联. 在开了某些优化时编译器会自动将一些简单的函数内联.
  • 形参出现在函数定义中用来占位,只有在调用时分配内存. 实参出现在函数调用时.在函数调用时实参的值被传递给形参.

C/C++的’\n’和std::endl是否刷新缓冲区的问题

如果cout输出到一个交互设备(interactive device), 如终端(terminal), 它们通常是行缓冲而非完全缓冲的, 所以’\n’会刷新缓冲区, 但如文件这种完全缓冲的目标, ‘\n’不会刷新缓冲区. 我提出这个问题并空想一番之后被M4tsuri丢了两个StackOverflow链接, 我去面壁了…
Does printf always flush the buffer on encountering a newline
does new line character also flush the buffer