ANSI C 特性与文件系统数据访问
1. ANSI C 预定义符号
在 ANSI C 中,预处理器提供了一些预定义符号,这些符号在编程中有着重要的作用:
-
__FILE__
:表示当前源文件,以带引号的字符串形式呈现。
-
__LINE__
:代表当前行号,为整数类型。
-
__DATE__
和
__TIME__
:这是 ANSI C 标准新增的,分别给出程序编译时的当前日期和时间,同样以带引号的字符串表示。
-
__STDC__
:在符合 ANSI C 标准的编译器中,该常量被定义为 1。可以利用它来测试是否可以使用 ANSI C 特性,示例代码如下:
#ifdef __STDC__
... ANSI stuff ...
#else
... Non-ANSI stuff ...
#endif
不过在 SVR4 系统中,AT&T 将
__STDC__
的值设为 0 以在非严格符合 ANSI C 的环境中启用某些 ANSI C 特性,所以上述测试需要改写为:
#if __STDC__ == 1
... ANSI stuff ...
#else
... Non-ANSI stuff ...
#endif
另外,对于
#else
和
#endif
后面跟参数的语法,在 ANSI C 中是明确禁止的,虽然大多数编译器会给出警告并接受,但应改写为如下形式:
#ifdef FOO
...
#else /* FOO */
...
#endif /* FOO */
2. ANSI C 变量声明的改进
ANSI C 对变量声明进行了规范,通过规范一些非标准类型的使用并定义了一些新类型,使变量声明更加清晰和准确。
-
void 类型
:在 ANSI C 中有三种用途:
1. 声明返回类型为
void
的函数,表示该函数不返回值,有助于编译器进行类型检查。
2. 函数原型中参数指定为
void
,表明函数没有参数,方便编译器检查函数调用时的参数列表。
3.
void *
现在作为通用指针使用,在不同对象使用不同大小指针的系统中比以前常用的
char *
更合适。
-
enum 类型
:ANSI C 正式将
enum
数据类型进行了规范,明确允许使用
enum
变量作为数组下标,而之前有些编译器不允许这样做。
-
char 类型
:由于硬件厂商对于
char
是有符号还是无符号没有统一标准,ANSI C 也未对此进行定义,
char
的有符号性或无符号性明确依赖于硬件。若需要特定类型(有符号或无符号),在声明
char
类型变量时可以使用熟悉的
unsigned
限定符和新的
signed
限定符。
3. ANSI C 类型限定符
ANSI C 定义了两个新的类型限定符:
-
const
:表示对象不会被修改,这让编译器可以拒绝修改该对象,同时也为编译器进行优化提供了更多的自由度。需要注意的是,初始化对象和修改对象是不同的概念,例如
const int True = 1;
是完全合法的。不过
const
限定符的使用有些复杂,例如
const char *s;
表示
s
只能指向不会通过
s
修改的字符,但
s
本身可能会被修改;若要声明
s
本身不会被修改,则应写成
char *const s;
。
-
volatile
:与
const
相反,它告知编译器该变量可能以编译器无法预测的方式改变,因此编译器不应优化对该变量的引用,因为在某些情况下优化可能不准确。
4. ANSI C 函数声明和调用的变化
ANSI C 在函数声明和调用方面有两个显著变化:
-
函数原型
:这是 ANSI C 最明显的变化之一,借鉴自 C++。通过函数原型,在声明函数时可以指定函数参数的数量和类型,使编译器能够进行类型检查,避免不必要的类型提升。函数原型有多种语法形式,例如:
FILE *fopen(char *filename, char *mode); // 最明确的形式
FILE *fopen(char *, char *); // 省略变量名
FILE *fopen(); // 旧的预 ANSI 语法
其中,旧的预 ANSI 语法编译器无法进行类型检查。函数定义可以遵循最明确的原型语法,也可以遵循旧的预 ANSI 语法,但每个参数的类型必须明确指定。对于有可变数量参数的函数,使用尾随的 “…” 表示,例如
int fprintf(FILE *, const char *, ...);
,且 “…” 必须在参数列表的最后。对于没有参数的函数,现在使用
void
类型声明,如
int getpid(void);
,这样编译器可以确保编译时不会向函数传递参数。
-
在非 ANSI 环境中处理原型
:即使使用 ANSI C 编译器,编写的代码也可能需要在没有 ANSI 编译器的系统上编译。可以采用以下几种方法:
- 最简单的方法是为每个函数编写两个声明:
#ifdef __STDC__
int fact(int);
#else
int fact();
#endif
#ifdef __STDC__
int fact(int n)
#else
int fact(n)
int n;
#endif
{
...
}
- 另一种方法是声明采用上述方式,但函数定义使用旧风格:
#ifdef __STDC__
int fact(int);
#else
int fact();
#endif
int fact(n)
int n;
{
...
}
- 更优雅的解决方案是定义一个宏(通常称为 `_P` 或 `_proto`)来处理原型,然后使用旧风格的定义:
#ifdef __STDC__
#define _P(args) args
#else
#define _P(args) ()
#endif
int fact _P((int));
int fact(n)
int n;
{
...
}
当
__STDC__
被定义时,原型扩展为
int fact (int);
;未定义时,扩展为
int fact ();
。
5. ANSI C 函数参数类型提升问题
在 K&R C 中,由于编译器无法对函数参数进行类型检查,会将所有小于
int
类型的参数提升为
int
,将
float
类型的参数提升为
double
。ANSI C 在函数调用时仍然会将函数参数提升为其扩展类型,但在函数内部会将扩展类型转换回原来的较窄类型,这可能会给编写不严谨的预 ANSI 代码带来严重问题。例如:
foo(f)
float f;
{
bar(&f);
}
bar(d)
double *d;
{
...
}
在预 ANSI C 中,
f
实际上被当作
double
处理,而在 ANSI C 中,虽然编译器不会给出警告,但程序执行时
bar
函数可能会出现问题。为避免此类问题,在编写既可以在有函数原型环境也可以在无函数原型环境使用的代码时,应只使用扩展类型,如使用
int
代替
char
或
short
,使用
double
代替
float
,不过指向任何类型(扩展或未扩展)的指针是可以的。
6. ANSI C 表达式求值规则的变化
在原始的 K&R C 中,
unsigned
只指定一种类型,没有
unsigned char
、
unsigned short
或
unsigned long
这些“官方”类型,不同编译器对于这些非官方类型在表达式中的行为规则不同。大多数 C 编译器使用“符号保留”规则,即无符号类型需要扩展时会扩展为更大的无符号类型,无符号类型与有符号类型混合时结果为无符号类型,这在某些情况下可能导致意外结果。而 ANSI C 指定使用“值保留”规则,当小于
int
的无符号类型需要扩展时,如果
signed int
足够大则扩展为
signed int
,否则扩展为
unsigned int
,这样在一些情况下行为更直观,但依赖旧行为的程序可能需要修改(通常通过插入适当的类型转换)才能正确工作。
以下是相关内容的 mermaid 流程图:
graph TD;
A[ANSI C编程] --> B[预定义符号使用];
A --> C[变量声明];
A --> D[类型限定符];
A --> E[函数声明与调用];
A --> F[表达式求值];
B --> B1[__FILE__];
B --> B2[__LINE__];
B --> B3[__DATE__];
B --> B4[__TIME__];
B --> B5[__STDC__];
C --> C1[void类型];
C --> C2[enum类型];
C --> C3[char类型];
D --> D1[const];
D --> D2[volatile];
E --> E1[函数原型];
E --> E2[非ANSI环境处理];
E --> E3[参数类型提升];
F --> F1[K&R C规则];
F --> F2[ANSI C规则];
综上所述,ANSI C 的这些变化总体上是有益的,它在几乎所有 UNIX 平台上迅速普及,使用其特性(如函数原型)可以使代码更具可移植性,减少出错的可能性。
ANSI C 特性与文件系统数据访问
7. 访问文件系统数据结构
在系统管理任务中,常常需要获取有关挂载文件系统的信息。以下将介绍相关的文件和函数。
7.1 挂载文件系统表
文件
/etc/mnttab
包含当前挂载的文件系统列表及其相关信息。在 SVR4 中,该文件是文本文件,每行代表一个条目;在其他大多数 UNIX 版本中,它是二进制文件。读取该文件使用的结构体
struct mnttab
定义在
sys/mnttab.h
中:
struct mnttab {
char *mnt_special;
char *mnt_mountp;
char *mnt_fstype;
char *mnt_mntopts;
char *mnt_time;
};
各字段含义如下:
| 字段 | 含义 |
| ---- | ---- |
|
mnt_special
| 文件系统所在的块特殊设备名称 |
|
mnt_mountp
| 文件系统的挂载点名称,即挂载的目录 |
|
mnt_fstype
| 文件系统类型,如 “ufs”、“nfs” 等 |
|
mnt_mntopts
| 文件系统挂载时使用的选项,以逗号分隔 |
|
mnt_time
| 文件系统挂载的时间,为包含
time_t
值的 ASCII 字符串 |
读取
/etc/mnttab
文件的三个函数如下:
#include <stdio.h>
#include <sys/mnttab.h>
int getmntent(FILE *fp, struct mnttab *mnt);
int getmntany(FILE *fp, struct mnttab *mnt, struct mnttab *mntref);
char *hasmntopt(struct mnttab *mnt, char *option);
-
getmntent:从fp引用的文件中读取下一个条目,并将条目的各个字段存储在mnt指向的区域。 -
getmntany:在fp引用的文件中搜索与mntref非空字段匹配的条目,并将匹配条目的字段存储在mnt指向的区域。 -
hasmntopt:扫描mnt的mnt_mntopts字段,查找与option匹配的子字符串,若存在则返回指向该子字符串的指针,否则返回NULL。
这两个读取函数若成功读取条目返回 0,遇到文件结束返回 -1,若文件格式错误则返回以下错误码之一:
-
MNT_TOOLONG
:文件中的一行超过最大行长。
-
MNT_TOOMANY
:文件中的一行包含过多字段。
-
MNT_TOOFEW
:文件中的一行包含的字段不足。
7.2 文件系统默认值文件
文件
/etc/vfstab
包含文件系统的 “默认” 信息,如设备名称、挂载点、挂载选项等。系统启动时会使用该表自动挂载文件系统,也可用于记录仅按需挂载的其他文件系统。在其他大多数 UNIX 版本(如 HP - UX 10.x 和 IRIX 5.x)中,该文件名为
/etc/fstab
,格式略有不同。
每行构成一个条目,由
struct vfstab
结构体描述,该结构体定义在
sys/vfstab.h
中:
struct vfstab {
char *vfs_special;
char *vfs_fsckdev;
char *vfs_mountp;
char *vfs_fstype;
char *vfs_fsckpass;
char *vfs_automnt;
char *vfs_mntopts;
};
各字段含义如下:
| 字段 | 含义 |
| ---- | ---- |
|
vfs_special
| 文件系统所在的块特殊设备名称 |
|
vfs_fsckdev
| 文件系统所在的字符特殊设备名称,用于
fsck
程序在启动时检查文件系统完整性 |
|
vfs_mountp
| 文件系统的挂载点名称 |
|
vfs_fstype
| 文件系统类型 |
|
vfs_fsckpass
|
fsck
运行时,指示该文件系统应在第几次检查 |
|
vfs_automnt
| 指示系统启动时是否自动挂载该文件系统 |
|
vfs_mntopts
| 挂载该文件系统时应使用的选项 |
读取
/etc/vfstab
文件的四个函数如下:
#include <stdio.h>
#include <sys/vfstab.h>
int getvfsent(FILE *fp, struct vfstab *vfs);
int getvfsfile(FILE *fp, struct vfstab *vfs, char *file);
int getvfsspec(FILE *fp, struct vfstab *vfs, char *spec);
int getvfsany(FILE *fp, struct vfstab *vfs, struct vfstab *vfsref);
-
getvfsent:从fp引用的文件中读取下一个条目,并将条目的各个字段存储在vfs指向的区域。 -
getvfsfile:在文件中搜索vfs_mountp字段与file相同的条目,并将其字段存储在vfs指向的区域。 -
getvfsspec:在文件中搜索vfs_special字段与spec相同的条目,并将其字段存储在vfs指向的区域。 -
getvfsany:在fp引用的文件中搜索与vfsref非空字段匹配的条目,并将匹配条目的字段存储在vfs指向的区域。
这四个函数若成功读取条目返回 0,遇到文件结束返回 -1,若文件格式错误则返回以下错误码之一:
-
VFS_TOOLONG
:文件中的一行超过最大行长。
-
VFS_TOOMANY
:文件中的一行包含过多字段。
-
VFS_TOOFEW
:文件中的一行包含的字段不足。
7.3 获取文件系统统计信息
可以使用
statvfs
和
fstatvfs
函数获取文件系统的统计信息,如文件系统的使用或可用空间、文件数量等。
#include <sys/types.h>
#include <sys/statvfs.h>
int statvfs(const char *path, struct statvfs *stats);
int fstatvfs(int fd, struct statvfs *stats);
-
statvfs:获取由path指定的文件所在文件系统的统计信息,并将其存储在stats指向的区域。 -
fstatvfs:功能与statvfs相同,但使用文件描述符而不是路径名来引用文件。
两个函数成功时返回 0,出错时返回 -1 并设置
errno
指示错误。它们返回的统计信息存储在
struct statvfs
结构体中:
typedef struct statvfs {
u_long f_bsize;
u_long f_frsize;
u_long f_blocks;
u_long f_bfree;
u_long f_bavail;
u_long f_files;
u_long f_ffree;
u_long f_favail;
u_long f_fsid;
char f_basetype[FSTYPSZ];
u_long f_flag;
u_long f_namemax;
char f_fstr[32];
u_long f_filler[16];
} statvfs_t;
各字段含义如下:
| 字段 | 含义 |
| ---- | ---- |
|
f_bsize
| 文件系统首选的块大小,读写操作使用该块大小可获得最佳性能 |
|
f_frsize
| 文件系统的基本块大小,也称为片段大小,是文件占用磁盘空间的最小单位 |
|
f_blocks
| 文件系统中可使用的总块数,以
f_frsize
为单位 |
|
f_bfree
| 文件系统中可用的总块数 |
|
f_bavail
| 非特权进程可用的文件系统空闲块数 |
|
f_files
| 文件系统中可创建的文件(inode)总数,NFS 挂载的文件系统该值不可用 |
|
f_ffree
| 文件系统中可用的文件(inode)总数,NFS 挂载的文件系统该值不可用 |
|
f_favail
| 非特权进程可用的文件(inode)总数,系统可能会为超级用户保留少量文件,NFS 挂载的文件系统该值不可用 |
|
f_fsid
| 文件系统的唯一标识符 |
|
f_basetype
| 文件系统类型名称 |
|
f_flag
| 标志位的位掩码,可能的值有
ST_RDONLY
(文件系统为只读)、
ST_NOSUID
(文件系统不支持 set - user - id 和 set - group - id 位语义)、
ST_NOTRUNC
(文件系统不截断超过最大长度的文件名) |
|
f_namemax
| 文件系统上文件名的最大长度 |
|
f_fstr
| 仅由内核使用的文件系统特定字符串 |
以下是一个示例程序
fsysinfo
,用于读取挂载文件系统表,查找文件系统在默认表中的信息,并获取其统计信息:
#include <sys/types.h>
#include <sys/statvfs.h>
#include <sys/time.h>
#include <string.h>
#include <stdio.h>
#include <sys/mnttab.h>
#include <sys/vfstab.h>
char *mnttabFile = "/etc/mnttab";
char *vfstabFile = "/etc/vfstab";
struct statvfs *getfsInfo(char *);
struct mnttab *getmnttabEntry(FILE *);
struct vfstab *getvfstabEntry(FILE *, struct mnttab *);
int
main(void)
{
time_t clock;
struct mnttab *mnt;
struct vfstab *vfs;
struct statvfs *stats;
FILE *mnttabFP, *vfstabFP;
/*
* Open the mounted file system table.
*/
if ((mnttabFP = fopen(mnttabFile, "r")) == NULL) {
perror(mnttabFile);
exit(1);
}
/*
* Open the file system defaults file.
*/
if ((vfstabFP = fopen(vfstabFile, "r")) == NULL) {
perror(vfstabFile);
exit(1);
}
/*
* For each file system...
*/
while ((mnt = getmnttabEntry(mnttabFP)) != NULL) {
/*
* If it's not an "ignore" file system, look it
* up in the defaults file and get its current
* stats.
*/
if (hasmntopt(mnt, "ignore") == 0) {
vfs = getvfstabEntry(vfstabFP, mnt);
stats = getfsInfo(mnt->mnt_mountp);
}
else {
stats = NULL;
vfs = NULL;
}
clock = atoi(mnt->mnt_time);
/*
* Print the mnttab structure.
*/
printf("%s:\n", mnt->mnt_mountp);
printf(" %s information:\n", mnttabFile);
printf(" file system type: %s\n", mnt->mnt_fstype);
printf(" mounted on device: %s\n", mnt->mnt_special);
printf(" mounted with options: %s\n", mnt->mnt_mntopts);
printf(" mounted since: %s", ctime(&clock));
/*
* Print the vfstab structure.
*/
if (vfs != NULL) {
printf(" %s information:\n", vfstabFile);
printf(" file system type: %s\n",
vfs->vfs_fstype ? vfs->vfs_fstype : "");
printf(" mount device: %s\n",
vfs->vfs_special ? vfs->vfs_special : "");
printf(" fsck device: %s\n",
vfs->vfs_fsckdev ? vfs->vfs_fsckdev : "");
printf(" fsck pass number: %s\n",
vfs->vfs_fsckpass ? vfs->vfs_fsckpass : "");
printf(" mount at boot time: %s\n",
vfs->vfs_automnt ? vfs->vfs_automnt : "");
printf(" mount with options: %s\n",
vfs->vfs_mntopts ? vfs->vfs_mntopts : "");
}
/*
* Print the statvfs structure.
*/
if (stats != NULL) {
printf(" statvfs information:\n");
printf(" maximum name length: %u\n", stats->f_namemax);
printf(" preferred block size: %u\n", stats->f_bsize);
printf(" fundam. block size: %u\n", stats->f_frsize);
printf(" total blocks: %u\n", stats->f_blocks);
printf(" total blocks free: %u\n", stats->f_bfree);
printf(" total blocks avail: %u\n", stats->f_bavail);
printf(" total files: %u\n", stats->f_files);
printf(" total files free: %u\n", stats->f_ffree);
printf(" total files avail: %u\n", stats->f_favail);
}
putchar('\n');
}
/*
* All done.
*/
fclose(mnttabFP);
fclose(vfstabFP);
exit(0);
}
/*
* getmnttabEntry - read an entry from the mount table.
*/
struct mnttab *
getmnttabEntry(FILE *fp)
{
int n;
static int line = 0;
static struct mnttab mnt;
/*
* Until we get a good entry...
*/
for (;;) {
/*
* Read the next entry.
*/
n = getmntent(fp, &mnt);
line++;
switch (n) {
case 0: /* okay */
return(&mnt);
case -1: /* end of file */
return(NULL);
case MNT_TOOLONG:
fprintf(stderr, "%s: %d: line too long.\n", mnttabFile, line);
break;
case MNT_TOOMANY:
fprintf(stderr, "%s: %d: too many fields.\n", mnttabFile, line);
break;
case MNT_TOOFEW:
fprintf(stderr, "%s: %d: not enough fields.\n", mnttabFile, line);
break;
}
}
}
/*
* getvfstabEntry - look up the file system defaults for the file system
* described by mnt.
*/
struct vfstab *
getvfstabEntry(FILE *fp, struct mnttab *mnt)
{
struct vfstab vfsref;
static struct vfstab vfs;
/*
* Have to rewind each time.
*/
rewind(fp);
/*
* Zero out the reference structure.
*/
memset((char *) &vfsref, 0, sizeof(struct vfstab));
/*
* Look for an entry that has the same special device,
* mount point, and file system type.
*/
vfsref.vfs_special = mnt->mnt_special;
vfsref.vfs_mountp = mnt->mnt_mountp;
vfsref.vfs_fstype = mnt->mnt_fstype;
/*
* Look it up.
*/
if (getvfsany(fp, &vfs, &vfsref) == 0)
return(&vfs);
return(NULL);
}
/*
* getfsInfo - look up information about the file system.
*/
struct statvfs *
getfsInfo(char *filsys)
{
static struct statvfs stats;
if (statvfs(filsys, &stats) < 0) {
perror(filsys);
return(NULL);
}
return(&stats);
}
运行该程序可以输出每个文件系统的相关信息,例如:
/:
/etc/mnttab information:
file system type: ufs
mounted on device: /dev/dsk/c0t3d0s0
mounted with options: rw,suid
mounted since: Mon Dec 5 09:05:28 1994
以下是访问文件系统数据结构的 mermaid 流程图:
graph TD;
A[访问文件系统数据] --> B[挂载文件系统表];
A --> C[文件系统默认值文件];
A --> D[获取文件系统统计信息];
B --> B1[打开/etc/mnttab];
B --> B2[读取条目];
B --> B3[检查选项];
C --> C1[打开/etc/vfstab];
C --> C2[查找匹配条目];
D --> D1[使用statvfs或fstatvfs];
D --> D2[获取统计信息];
总之,了解 ANSI C 的特性以及如何访问文件系统数据结构对于 UNIX 系统编程非常重要。合理运用 ANSI C 的新特性可以提高代码的质量和可移植性,而掌握文件系统数据的访问方法则有助于进行系统管理和监控等任务。
超级会员免费看
719

被折叠的 条评论
为什么被折叠?



