【C语言】C语言的495个问题

文章目录

  • 1 声明和初始化
    • 基本类型
      • 1.1 各类型区别
      • 1.2 为什么不精确定义标准类型的大小
      • 1.3 因为C没有精确定义标准类型大小,那么用typedef定义int16和int32是否能解决问题呢
      • 1.4 新64位机上64位类型是什么样的
      • 1.5 `char *p1, p2`有什么问题
      • 1.6 malloc用法
    • 声明风格
      • 1.7 全局变量和函数应该怎么定义&&声明?
      • 1.8 怎么在C中实现不透明数据类型?
      • 1.9 怎么生成半全局变量,就是那种只能被部分源文件中国的函数访问的变量?
    • 存储类型
      • 1.10 static函数内的变量都必须是static的吗
      • 1.11 extern 函数声明的作用
      • 1.12 auto的作用
    • 类型定义typedef
      • 1.13用户定义的类型,`typedef`和`#define`的区别?
      • 1.14 定义链表报错
      • 1.15 定义一对相互引用的结构
      • 1.16 struct定义和typedef struct定义的区别
      • 1.17 `typedef int (*funcptr) ();`是什么意思?
      • 1.18 const限定词
      • 1.19 为什么数组 维度值中,不能用const值
      • 1.20 `const char *p, char const *p, char *const p`的区别?
    • 复杂的声明
      • 1.21 非常复杂的声明怎么理解,定义一个包含N个指向返回指向字符的指针的函数的指针的数组?
      • 1.22 用C定义状态机
    • 数组大小
      • 1.23 能不能声明和传入数组大小一直的局部数组,或者有参数指定大小的参数数组?
      • 1.24 file1.c中定义extern数组,在file2.c中,为什么用sizeof取不到数组大小
    • 声明问题
      • 1.25 函数定义一次,调用一次,但编译器提示非法重复声明?
      • 1.26 void main()对吗
      • 1.27 编译器包函数原型不匹配
      • 1.28 文件中第一个声明就爆出奇怪的语法错误,为什么
      • 1.29 编译器不允许我定义大数组,如`double array[256][256]`
    • 命名空间
      • 1.30 怎么判断哪些标识符可以用,哪些被保留了
    • 初始化
      • 1.31 没有显示初始化的值?全局变量初始化为0?
      • 1.32 为什么不能编译如下char[]代码
      • 1.33 编译器提示invalid initializers
      • 1.34 以下初始化的区别
      • 1.35 char{a[3]} = "abc"是合法的
      • 1.36 函数指针怎么初始化
      • 1.37 能初始化union联合体吗
  • 2 struct,union和enum的区别
    • 结构声明
      • 2.1 struct和typedef struct的区别
      • 2.2 struct和typedef struct的使用
      • 2.3 struct可以包含指向自己的指针么?
      • 2.4 C语言,用什么方法实现抽象数据类型最好?
      • 2.5 C能否实现面向对象程序的一些好特性
      • 2.6 为啥声明报错
      • 2.7 struct内数组初始化很小的size,但实际装很大的容量,是一种通用的技术
      • 2.8 struct变量作为函数的入参和出参,是合法的
      • 2.9 为什么不能用==和!=来做比较
      • 2.10 传参是struct时,内部发生了什么
      • 2.11 如何解说struct做传参,怎样创建无名的中间常量struct
      • 2.12 怎样读写文件
    • 结构填充
      • 2.13 内存对齐
      • 2.15 如何确定域在struct内的字节偏移量
      • 2.16 怎样在运行中用名字,访问结构中的域
      • 2.18 数组名可用作数组的基地址,但为啥struct不能这样呢?
      • 2.19 程序运行正确,但退出时core dump了,为啥?
    • union
      • 2.20 struct与union的区别
      • 2.21 有办法初始化union吗
      • 2.22 有没有办法跟踪union的哪个域,在使用?
    • enum
      • 2.23 enum和一组预处理的`#define`有啥区别?
      • 2.24 enum可移植吗
      • 2.25 有什么显示enum符号的容易方法吗?
      • 2.26 位域: 冒号和数字是什么意思
      • 2.27 为什么大家喜欢用显示的掩码和位操作,而不是用位域
  • 3 表达式
    • 求值顺序
      • 3.1 `a[i] = i++`是未定义的
      • 3.2 `printf("%d\n", i++ * i++);`的结果出乎意料
      • 3.3 `i = i++`行为未定义
      • 3.4 `a ^= b ^= a ^= b`行为未定义
      • 3.5 不能通过括号强制执行计算顺序
      • 3.6 &&和||是有短路原则的
      • 3.8 函数调用的参数,的求值顺序是不确定的
      • 3.9 怎样避免写出“未定义”的表达式
      • 3.11 写简单表达式的原则
    • 其他的表达式问题
      • 3.13 i++与++i
      • 3.15 比较三个数的大小
      • 3.16 类型溢出或截短
  • 4 指针
    • 基本的指针应用
      • 4.1 指针的好处

因为需要研究PostgreSQL源码,故需要了解C的工程知识,虽说C的基础很简单,语法集合很少,但是对于大型系统来说,但由于C缺少一些现代语言的包,类等概念,在源码经常看到一些约定俗成的“利用C来实现高级语言语法特性”的知识,在《C语言的495个问题》中,有详细的说明,下面我们就一起看看吧。

1 声明和初始化

基本类型

1.1 各类型区别

  • 正确如下,已在mac和ubuntu中验证(书中说法是至少,但实际按如下记即可)
  • char 1byte
  • short int
  • int 4 byte
  • long int 8 byte
  • long long 8 byte
# include 

int main() {
    printf("char: %lu\n", sizeof(char)); // 1
    printf("short int: %lu\n", sizeof(short int)); // 2
    printf("int: %lu\n", sizeof(int)); // 4
    printf("long: %lu\n", sizeof(long)); // 8
    printf("long long: %lu\n", sizeof(long long)); // 8
    return 0;
}

1.2 为什么不精确定义标准类型的大小

C语言相对低级,其设计理念认为类型大小应该有具体实现来决定, 虽然这很容易出矛盾

1.3 因为C没有精确定义标准类型大小,那么用typedef定义int16和int32是否能解决问题呢

是的,但其实标准头文件中已经定义了int16_tuint_32_t类型

1.4 新64位机上64位类型是什么样的

  • 增加了long long: 至少64bit

1.5 char *p1, p2有什么问题

  • 这样表示p1是指针,但p2是值。
  • 应改为char *p1, *p2;

1.6 malloc用法

正确方式如下, 注意malloc函数返回的是指针

#include 

int main() {
    char *p;
    p = malloc(10);
    return 0;
}

声明风格

1.7 全局变量和函数应该怎么定义&&声明?

  • 可以声明多次,但只能定义一次。
  • 一般在.c文件中定义,在.h文件·中声明,这样使用者只要引用了.h文件就拥有了声明。
    • 变量声明是extern int i;
    • 函数声明是extern int f();
    • 变量定义是int i = 0;
    • 函数定义是int f() {return 1;}
    • 尤其是把外部函数的声明放在.h中,而不是.c中,防止外部函数原型变化了,但.c文件中忘记更改导致错误的函数原型贻害无穷。
  • c文件一般也要引用对应的h文件,这样编译器可以检查c和h文件中声明与定义是否一致。

1.8 怎么在C中实现不透明数据类型?

见2.4

1.9 怎么生成半全局变量,就是那种只能被部分源文件中国的函数访问的变量?

  • C原生不支持,
  • 为了实现变量分包来管理, 有2种解决方案:
    • 包内的变量都带固定前缀,“口头约定”使用者不能定义相同前缀的变量
    • 变量用下划线开头

存储类型

1.10 static函数内的变量都必须是static的吗

尽量这样做

1.11 extern 函数声明的作用

  • 可以当做一种格式上的提示,指明函数的定义可能在别的c文件中
  • 其实extern int f();int f();没有区别

1.12 auto的作用

没啥用,过时了

类型定义typedef

1.13用户定义的类型,typedef#define的区别?

  • 尽量用typedef,因为能更好的处理指针类型
  • #define主要是配合#ifdef宏使用的

1.14 定义链表报错

  • struct可以包含“指向 自己类型 的指针”,但要提前声明该struct类型
  • 以下三种均可
struct node {
    char *item;
    struct node *next;
};
typedef struct node *NodePtr;

int main() {
    NodePtr a;
    NodePtr b;
    return 1;
}
struct node;
typedef struct node *NodePtr;
struct node {
    char *item;
    NodePtr *next;
};

int main() {
    NodePtr a;
    NodePtr b;
    return 1;
}
typedef struct node {
    char *item;
    struct node *next;
} *NodePtr;

int main() {
    NodePtr a;
    NodePtr b;
    return 1;
}

1.15 定义一对相互引用的结构

以下两种均可

struct a {
    int field;
    struct b *bpointer;
};

struct b {
    int bfield;
    struct a *apointer;
};

int main() {
    struct a aa;
    struct b bb;
    return 1;
}
struct a;
struct b;
typedef struct a *APTR;
typedef struct b *BPTR;
struct a {
    int afield;
    BPTR bpointer;
};
struct b {
    int bfield;
    APTR pointer;
};

int main() {
    APTR aa;
    BPTR bb;
    return 1;
}

1.16 struct定义和typedef struct定义的区别

见2.1

1.17 typedef int (*funcptr) ();是什么意思?

  • 函数指针,传参为空,返回int
  • 等价于如下
funcptr fp1, fp2;
int (*pf1) (), (*pf2) ();

1.18 const限定词

typedef char *charp;
const charp p;

const修饰的是指针p,而不是指针p所指向的变量

1.19 为什么数组 维度值中,不能用const值

const int n = 5;
int a[n];

是不允许,会报错

1.20 const char *p, char const *p, char *const p的区别?

见11.10和1.21

复杂的声明

1.21 非常复杂的声明怎么理解,定义一个包含N个指向返回指向字符的指针的函数的指针的数组?

  • 可以用逐层定义

1.22 用C定义状态机

数组大小

1.23 能不能声明和传入数组大小一直的局部数组,或者有参数指定大小的参数数组?

不能

1.24 file1.c中定义extern数组,在file2.c中,为什么用sizeof取不到数组大小

// file1.c
int array[] = {1,2,3};
// file2.c
extern int array[];
  • 未指定数组大小的extern数组,是不完全类型,不能用sizeof
  • 有如下三种方案
// file1.c
int array[] = {1, 2, 3};
int arraysz = sizeof(array);

// file2.c
# include 
# include 
extern int array[];
extern int arraysz;

int main() {
//    int sz = sizeof(array);
//    printf("%d\n", sz);
    printf("%d\n", arraysz);
    return 1;
}
// file1.h
#define ARRAYSZ 3

// file1.c
# include "file1.h"
int array[ARRAYSZ] = {1, 2, 3};

// file2.c
# include "file1.h"
# include 
extern int array[];

int main() {
    printf("%d\n", ARRAYSZ);
    return 1;
}

或者给数组末尾加哨兵位(如-1, NULL等)

声明问题

1.25 函数定义一次,调用一次,但编译器提示非法重复声明?

可能是函数没有声明,或者和引入的某头文件中的函数同名了。

1.26 void main()对吗

不对,见11.7

1.27 编译器包函数原型不匹配

见11.4

1.28 文件中第一个声明就爆出奇怪的语法错误,为什么

见10.9

1.29 编译器不允许我定义大数组,如double array[256][256]

见19.28和7.20

命名空间

1.30 怎么判断哪些标识符可以用,哪些被保留了

略 不要用下划线开头,容易出问题

初始化

1.31 没有显示初始化的值?全局变量初始化为0?

  • static变量,未初始化时,C可以确保初始值是0
  • 全局和局部变量,未初始化时,内容是未定义的垃圾内容,不能做任何有用的假设(包括用malloc和realloc申请的堆内存,也需要手动做初始化,否则也是未定义的垃圾内容)

1.32 为什么不能编译如下char[]代码

如果char[] a = "";不行的话,可以用char *a = "";或者char a[10]; strcpy(a, "");

1.33 编译器提示invalid initializers

不明白

1.34 以下初始化的区别

char[] a = "";char *a = "";,字符串字面量是内存中只读区域的,有的编译器会控制其是否可写,所以对a的写操作是否报错,取决于编译器的种类

1.35 char{a[3]} = "abc"是合法的

对,是合法的

1.36 函数指针怎么初始化

extern int func();
int (*fp)() = func;

fp是指向函数的指针

1.37 能初始化union联合体吗

是,可以

2 struct,union和enum的区别

结构声明

2.1 struct和typedef struct的区别

用struct定义的话,使用时也得带上struct关键字,反之则不用

2.2 struct和typedef struct的使用

struct x {};
struct x a;

// 或
typedef struct x{} y;
y a;

// 或可以重名
typedef struct x{} x;
x a;

2.3 struct可以包含指向自己的指针么?

可以,见1.14和1.15

2.4 C语言,用什么方法实现抽象数据类型最好?

不明

2.5 C能否实现面向对象程序的一些好特性

  • 把函数指针,放入struct,可以实现“类的方法”
  • 继承:通过预处理器,或让积累的结构作为初始的子集
  • 没有重载和覆盖

2.6 为啥声明报错

见11.6

2.7 struct内数组初始化很小的size,但实际装很大的容量,是一种通用的技术

通过struct,然后提供一个函数,可以初始化数组, 详细见数上的4个例子

2.8 struct变量作为函数的入参和出参,是合法的

这种方式传递的是浅拷贝,struct内的指针成员指向的还是原地址,而不是复制出新的一份儿

2.9 为什么不能用==和!=来做比较

遇到未使用的hole洞时会出问题,所以一般char*都是用strcmp比较而不是==

2.10 传参是struct时,内部发生了什么

  • 传参是struct时,会把struct推到stack里,所以浪费stack空间
  • 因此一般都传指针,而不是传struct

2.11 如何解说struct做传参,怎样创建无名的中间常量struct

可以用匿名struct

plotpoint((struct point){1,2});plotpoint((struct point){.x=1, .y=2});

2.12 怎样读写文件

  • fwrite()
  • fread()
  • 注意移植性

结构填充

2.13 内存对齐

  • 当内存对齐时,很多机器能更高效的访问,因此编译器会的struct做内存对齐
  • 比如按byte寻址的机器,2字节的short int必须放在偶数地址上,4byte的long int必须放在整数倍地址上
  • 有些机器压根不能访问没有内存对齐的机器
  • sizeof返回的是对齐后的值

2.15 如何确定域在struct内的字节偏移量

# define offsetof(type, f) ((size_t) \ ((char *)&((type *)0)->f -(char *)(type *) 0))

2.16 怎样在运行中用名字,访问结构中的域

offsetb = offsetof(struct a, b);
*(int *) ((char *)structp + offsetb) = value;

2.18 数组名可用作数组的基地址,但为啥struct不能这样呢?

  • 数组,函数都如此
  • 当你提到结构时,得到的是整个结构

2.19 程序运行正确,但退出时core dump了,为啥?

注意struct定义的末尾要加分号;

union

2.20 struct与union的区别

  • union本质上是成员相互重叠的struct,可以从一种类型解释,亦可以从另一种类型解释。
  • union的大小,是其最大成员的大小。
  • struct的大小,是其所有成员大小之和。
  • union和struct都有可能有内存对齐。

2.21 有办法初始化union吗

可以初始化union的任意成员

2.22 有没有办法跟踪union的哪个域,在使用?

没有

enum

2.23 enum和一组预处理的#define有啥区别?

  • 区别很小,enum可读性更好

2.24 enum可移植吗

移植性很好

2.25 有什么显示enum符号的容易方法吗?

没有,可以自己写一个函数,把枚举常量值映射到字符串,或者用调试器

2.26 位域: 冒号和数字是什么意思

节省内存空间,用于struct内部

2.27 为什么大家喜欢用显示的掩码和位操作,而不是用位域

因为位域的移植性差

3 表达式

求值顺序

3.1 a[i] = i++是未定义的

3.2 printf("%d\n", i++ * i++);的结果出乎意料

尽量不要在一行里写多个i++之类的值,和人想象的结果可能有差异

3.3 i = i++行为未定义

最好不要这么写

3.4 a ^= b ^= a ^= b行为未定义

尽量不要这么写,在一个表达式中两次修改变量a的值,此行为是未定义的

3.5 不能通过括号强制执行计算顺序

只有部分作用

3.6 &&和||是有短路原则的

即若左边的子表达式即可决定最终结果,则右边的子表达式不会计算
如下是非常常见的正确用法

if (d != 0 && n / d > 0) {
    // sth
}

if (p == NULL || *p == '\0') {
    // sth
}

3.8 函数调用的参数,的求值顺序是不确定的

如下,可能f2()比f1()先执行

printf("%d %d", f1(), f2());

3.9 怎样避免写出“未定义”的表达式

书中的表述有些绕,就是尽量不要写类似a[i] = i++之类的复杂表达式,避免歧义类似

3.11 写简单表达式的原则

  1. 一个表达式最多只修改一个对象
  2. 如果一个对象在一个表达式中出现一次以上,且在表达式中被修改,应确保每次的读操作都读取的是最终值

其他的表达式问题

3.13 i++与++i

  • i++: i+1,返回的是原i值
  • ++i:i+1,返回的是+1后的值

3.15 比较三个数的大小

正确的方式是

if (a < b && b < c) {
}

而不是if(a

3.16 类型溢出或截短

正确的方式是提前转换,如下是正确的

long int c = (long int) a * b;
// 或
long int c = (long int) a * (long int) b;

4 指针

基本的指针应用

4.1 指针的好处

你可能感兴趣的:(#,c,c语言,开发语言)