Linux 编程之typedef

Linux 编程之typedef

文章目录

  • Linux 编程之typedef
    • 概述
    • 一些实例使用场景
      • typedef定义结构体
      • typedef定义数组和指针
      • typedef定义结构体指针
      • typedef定义函数指针
      • 类型转换
    • 禁止使用情况
    • typedef与#define区别
    • Linux对typedef编程风格建议

概述

在C和C++编程语言中,typedef是一个关键字。它用来对一个资料类型取一个别名,目的是为了使源代码更易于阅读和理解。它通常用于简化声明复杂的类型组成的结构 ,但它也常常在各种长度的整数资料类型中看到,例如size_t和time_t。

一些实例使用场景

typedef定义结构体

在我们使用一些嵌入式mcu的时候,会经常用到一些typedef的结构体,比如stm32的spi的头文件中,就定义了如下的结构体。

typedef struct
{
  uint16_t SPI_Direction;           /*!< Specifies the SPI unidirectional or bidirectional data mode.
                                         This parameter can be a value of @ref SPI_data_direction */
  uint16_t SPI_Mode;                /*!< Specifies the SPI operating mode.
  uint16_t SPI_FirstBit;            /*!< Specifies whether data transfers start from MSB or LSB bit.
                                         This parameter can be a value of @ref SPI_MSB_LSB_transmission */
  uint16_t SPI_CRCPolynomial;       /*!< Specifies the polynomial used for the CRC calculation. */
}SPI_InitTypeDef;

然后在c文件中使用如下:

void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct)
{
  uint16_t tmpreg = 0;
  /* check the parameters */
  /*---------------------------- SPIx CR1 Configuration ------------------------*/
  /* Get the SPIx CR1 value */
  tmpreg = SPIx->CR1;
  /* Clear BIDIMode, BIDIOE, RxONLY, SSM, SSI, LSBFirst, BR, MSTR, CPOL and CPHA bits */
  tmpreg &= CR1_CLEAR_Mask;
  /* Write to SPIx CR1 */
  SPIx->CR1 = tmpreg;
  /* Activate the SPI mode (Reset I2SMOD bit in I2SCFGR register) */
  SPIx->I2SCFGR &= SPI_Mode_Select;		
  /*---------------------------- SPIx CRCPOLY Configuration --------------------*/
  /* Write to SPIx CRCPOLY */
  SPIx->CRCPR = SPI_InitStruct->SPI_CRCPolynomial;
}

然后我们在使用的时候就可以使用下面的代码进行初始化。

  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//软件NSS 
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; //4分频
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据位的低位在前
  SPI_InitStructure.SPI_CRCPolynomial = 7;//CRC7
  SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//将SPI配置为主
  SPI_Init(SPI2,&SPI_InitStructure);//根据指定配置参数初始化SPIX寄存器

typedef定义数组和指针

typedef可以简单的跟数组一起使用。例如 :

typedef char arrType[6];    // type name: arrType
                            // new type: char[6]
arrType arr={1,2,3,4,5,6};  // same as: char arr[6]={1,2,3,4,5,6}
arrType *pArr;              // same as: char (*pArr)[6];

在这里,arrTypechar[6]的别称。而arrType *pArr;则表示pArr是一个指向存储char[6]类型存储器的指针。

可以使用typedef来定义一个新的指针类型 :

typedef int *intptr;   // type name: intptr
                       // new type: int*
intptr ptr;            // same as: int *ptr

在上面那段代码中,intptr是一个 指针类型int*的 新的别名。intptr ptr;声明了一个变量(ptr),其资料类型是int*。如此一来ptr就是一个 可以指向一段存储int资料的存储器 的指针了。 使用typedef来定义一个新的指针类型有时候会造成一些困惑 :

typedef int *intptr;
intptr cliff, allen;        // both cliff and allen are int* type
intptr cliff2, *allen2;     // cliff2 is int* type, but allen2 is int** type
                            // same as: intptr cliff2;
                            //          intptr *allen2;

在上面的代码中,ntptr cliff, allen;表示声明两个变量,其资料类型是int,而intptr allan2则使allen2的类型成为int*

typedef定义结构体指针

Typedef可以跟结构体指针一起使用。如下 :

struct Node {
    int data;
    struct Node *nextptr;
};

使用typedef可以改写成如下 :

typedef struct Node Node;
struct Node {
    int data;
    Node *nextptr;
};

在C语言中,可以在一行中声明复数的变量,不管其是不是指针。不管如何,如果你要声明指针,必须在每个变量前面加上星号。 在下面的代码中,工程师可能会以为errptr是一个指针,这个能会引起一些错误。

struct Node *startptr, *endptr, *curptr, *prevptr, errptr, *refptr;

如果你用typedef定义一个Node *,这可以保证所有的变量都是一个指向一个structure type的指针。

typedef struct Node* NodePtr;
...
NodePtr startptr, endptr, curptr, prevptr, errptr, refptr;

typedef定义函数指针

先看看以下这段尚未使用typedef的代码:

int do_math(float arg1, int arg2) {
    return arg2;
}
int call_a_func(int (*call_this)(float, int)) {
    int output = call_this(5.5, 7);
    return output;
}
int final_result = call_a_func(&do_math);

这段代码可以被改写成如下:

typedef int (*MathFunc)(float, int);
int do_math(float arg1, int arg2) {
    return arg2;
}
int call_a_func(MathFunc call_this) {
    int output = call_this(5.5, 7);
    return output;
}
int final_result = call_a_func(&do_math);

在这里,MathFunc是一个指针,指向一个回传int并以一个float和一个int作为参数使用的函数。当一个函数被当作参数使用时,如果少了typedef它可能会变得难以了解。 以下是signal(3)(来自FreeBSD)的函数原型:

void (*signal(int sig, void (*func)(int)))(int);

上面声明的函数相当的神秘,因为它没有清楚的显示它以什么函数当作参数,或回传了什么资料类型。一个初心者工程师甚至可能以为它接收一个int作为参数,并不回传任何东西。但它其实接收了一个int和一个function pointer作为参数,并回传了一个function pointer。它可以被改写成以下代码:

typedef void (*sighandler_t)(int);
sighandler_t signal(int sig, sighandler_t func);

改写之后,这个代码理解起来就变得容易多了。

类型转换

typedef同时可以用来类型转换。例如:

typedef int (*funcptr)(double);         // pointer to function taking a double returning int
funcptr x = (funcptr) NULL;             // C or C++
funcptr y = funcptr(NULL);              // C++ only
funcptr z = static_cast<funcptr>(NULL); // C++ only

左侧,funcptr用来声明变量;右侧,funcptr则用来转换值的类型。 如果少了typedef,替换使用声明语法和类型转换语法是几乎不能做到的。例如:

void *p = NULL;
int (*x)(double)  = (int (*)(double)) p; // This is legal
int (*)(double) y = (int (*)(double)) p; // Left-hand side is not legal
int (*z)(double)  = (int (*p)(double));  // Right-hand side is not legal

禁止使用情况

typedef本身是一种存储类的关键字,与auto、extern、static、register等关键字不能出现在同一个表达式中。在C99的协议规范中可以看到有如下的定义。

6.7.1 Storage-class specifiers
Syntax
storage-class-specifier:
    typedef
    extern
    static
    auto
    register

Constraints: At most, one storage-class specifier may be 
             given in the declaration specifiers in a declaration

Semantics: The typedef specifier is called a ‘‘storage-class specifier’’ 
           for syntactic convenience only; 

因此如果我们尝试将一些都是存储类型对应的变量整合起来是不行的,比如:

typedef static int sint 这样的定义是会报错的。

这是因为c语言语法约束:最多可以在声明中的声明说明符中给出一个storage-classor-typedef-specifier

typedef与#define区别

两者的区别如下:

#define进行简单的进行字符串替换。 #define宏定义可以使用#ifdef、#ifndef等来进行逻辑判断,还可以使用#undef来取消定义。
typedef是为一个类型起新名字。typedef符合(C语言)范围规则,使用typedef定义的变量类型,其作用范围限制在所定义的函数或者文件内(取决于此变量定义的位置),而宏定义则没有这种特性。
通常,使用typedef要比使用#define要好,特别是在有指针的场合里。

Linux对typedef编程风格建议

但是在Linux中,是不建议使用类似 vps_t 之类的东西。对结构体和指针使用 typedef 很容易引起歧义。当你在代码里看到:

vps_t a;

这代表什么意思呢?相反,如果是这样,你就知道 a 是什么了。

struct virtual_container *a;

很多人认为 typedef “能提高可读性”。但是在实际中,它们只在下列情况下建议使用:

  1. 完全不透明的对象(这种情况下要主动使用 typedef来隐藏这个对象实际上是什么)。

    例如:pte_t 等不透明对象,你只能用合适的访问函数来访问它们。

    注意!不透明性和“访问函数”本身是不好的。我们使用 pte_t 等类型的原因在于真的是 完全没有任何共用的可访问信息。

  2. 清楚的整数类型,如此,这层抽象就可以帮助消除到底是 int 还是 long 的混淆。

    u8/u16/u32 是完全没有问题的 typedef,不过它们更符合类别(d)而不是这里。

    再次注意!要这样做,必须事出有因。如果某个变量是 unsigned long,那么没有必要

     typedef unsigned long myflags_t;
    

    不过如果有一个明确的原因,比如它在某种情况下可能会是一个 unsigned int 而在 其他情况下可能为 unsigned long,那么就不要犹豫,请务必使用 typedef

  3. 当你使用 sparse 按字面的创建一个新类型来做类型检查的时候。

  4. 和标准 C99 类型相同的类型,在某些例外的情况下。

    虽然让眼睛和脑筋来适应新的标准类型比如 uint32_t 不需要花很多时间,可是有些 人仍然拒绝使用它们。

    因此,Linux 特有的等同于标准类型的 u8/u16/u32/u64 类型和它们的有符号类型是被 允许的——尽管在你自己的新代码中,它们不是强制要求要使用的。

    当编辑已经使用了某个类型集的已有代码时,你应该遵循那些代码中已经做出的选择。

  5. 可以在用户空间安全使用的类型。

    在某些用户空间可见的结构体里,我们不能要求 C99 类型而且不能用上面提到的 u32 类型。因此,我们在与用户空间共享的所有结构体中使用 __u32 和类似的类型。

结论:

可能还有其他的情况,不过基本的规则是永远不要使用 typedef,除非你可以明确的应用上述某个规则中的一个。

总的来说,如果一个指针或者一个结构体里的元素可以合理的被直接访问到,那么它们就不 应该是一个 typedef

你可能感兴趣的:(Linux编程,编程语言,linux,typedef,指针)