44、ANSI C 特性与文件系统数据访问

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 的新特性可以提高代码的质量和可移植性,而掌握文件系统数据的访问方法则有助于进行系统管理和监控等任务。

考虑柔性负荷的综合能源系统低碳经济优化调度【考虑碳交易机制】(Matlab代码实现)内容概要:本文围绕“考虑柔性负荷的综合能源系统低碳经济优化调度”展开,重点研究在碳交易机制下如何实现综合能源系统的低碳化经济性协同优化。通过构建包含风电、光伏、储能、柔性负荷等多种能源形式的系统模型,结合碳交易成本能源调度成本,提出优化调度策略,以降低碳排放并提升系统运行经济性。文中采用Matlab进行仿真代码实现,验证了所提模型在平衡能源供需、平抑可再生能源波动、引导柔性负荷参调度等方面的有效性,为低碳能源系统的设计运行提供了技术支撑。; 适合人群:具备一定电力系统、能源系统背景,熟悉Matlab编程,从事能源优化、低碳调度、综合能源系统等相关领域研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①研究碳交易机制对综合能源系统调度决策的影响;②实现柔性负荷在削峰填谷、促进可再生能源消纳中的作用;③掌握基于Matlab的能源系统建模优化求解方法;④为实际综合能源项目提供低碳经济调度方案参考。; 阅读建议:建议读者结合Matlab代码深入理解模型构建求解过程,重点关注目标函数设计、约束条件设置及碳交易成本的量化方式,可进一步扩展至多能互补、需求响应等场景进行二次开发仿真验证。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值