typedef函数指针和extern关键字

时间:2014-09-20 18:36:28

标签: c function-pointers typedef extern

我在使用typedef理解指向函数的指针的语法时遇到了问题。我已经阅读了很多答案,但仍然无法理解。 我会尝试解释我是如何看待事物的,这样你才能理解我的想法。

因此我们使用typedef为现有类型提供别名,例如:

typedef int number;

这样我们可以使用与整数相同的数字(类似于预处理器指令 - 我知道在制作指针的typedef时会有一些差异)。 另一个例子:

typedef struct
{
    int num;
} MyStruct;

将为未命名的结构提供名为MyStruct的别名。

所以这是指向函数typedef的指针的语法:

typedef int (*pFunc)(int, int);

也许我很难理解这一点,因为typedef就像它给TYPES提供别名的名字而且函数不是完全类型但是无论如何,根据我的理解,这更像是指向某种函数签名的指针,所以第一个int是返回的类型,第二个括号是指示传递给函数的参数是什么类型。 现在我不太明白的是这部分:

(*pFunc)
  • 我认为是,我们创建一个新类型(使用typedef) 命名为pFunc,它是一个指针,它是*的作用。现在我们 可以创建这种类型的变量,指向任何函数 我们描述的签名。我是对的吗?

好吧,说我是对的,通常指向一些内存的指针声明如下:

int *p;
double *p;
.
.
.

因此,按照以下方式进行操作会更有意义:

(pFunc*)

因为对我而言,如果星号位于名称之前,它看起来像pFunc是某种类型的类型指针的变量名,而不是实际的类型指针。

  • 我们能做到吗?如果是这样,它是否常用于放星号 之前和之后?如果之前更常见的是为什么呢 就是它 ?因为就像我说的那样,我们总是定义一个指针类型 将星号放在名称后面就像上面的例子一样,为什么呢 那究竟是什么?
  • 关于这个的另一个问题,我不太明白是什么 围绕* pFunc括号的工作。 我认为他们用来表示某事物的pFunc类型指针,如果我们不做,则将括号括起来然后返回类型 签名的类型是int *而不是int,我是 对吗?

好吧,另一件令我困扰的事情就是语法的顺序。 到目前为止,在所有typedef定义中,我们左边是类型,右边是别名。

typedef int number;

typedef struct
{
    int num;
} MyStruct;

我们看到int和struct是左边的类型,我们给它们的别名在右边。

现在,在指向函数typedef的指针中,它不遵循此约定。 我们在右边的函数返回了类型,然后是括号中的typename,然后是括号中的参数类型,这个顺序让我在查看其他typedef如何处理相同的顺序后感到困惑。

  • 做这样的事情会不会更有意义? :

    typedef int (int,int) Func;所以我们首先有一个typedef,我们想要给别名的类型,在这种情况下是一个函数 签名,取2个int并返回一个int,然后在右边 我们有别名。它会更有意义吗?这跟着 其他typedef命令,我只是没有得到函数指针的顺序 多.. ..

  • 我的另一个问题:当我们制作指向函数的指针时,是什么 它真的意味着什么?我知道我们可以使用这个函数调用 我们的别名,但变量指针存储在内存地址中 ?有什么可以存储功能吗?
  • 最后,我读过关键字extern与之有关 函数指针,但无法理解这个关键字的作用, 有人可以向我解释它的作用吗?

3 个答案:

答案 0 :(得分:6)

typedef使用相同的语法来声明类型,通常用于声明值。

例如,如果我们声明名为int的{​​{1}},我们会这样做:

myInt

如果我们要将名为int myInt; 的类型声明为int,我们只需添加myIntType

typedef

我们可以声明一个函数typedef int myIntType; ,如下所示:

myFunc

告诉编译器,我们可以调用具有该名称和签名的实际函数。

我们也可以通过以下方式声明函数类型int myFunc(int a, int b);

myFuncType

我们可以这样做:

typedef int myFuncType(int a, int b);

这相当于先前的myFuncType myFunc; 声明(尽管很少使用此表格)。

功能不是传统值;它表示带有入口点地址的代码块。像上面那样的函数声明是隐式的myFunc;他们告诉编译器命名的东西存在于其他地方。但是,您可以获取函数的地址,该函数称为函数指针。函数指针可以指向具有正确签名的任何函数。通过在类型/值的名称前加extern来声明指针,因此,我们可能会尝试:

*

但这是不正确的,因为int *myFuncPtr(int a, int b); *绑定得更紧密,所以我们声明int是一个返回myFuncPtr指针的函数。我们必须在指针和名称周围放置parens以更改绑定顺序:

int

要声明类型,我们只需在前面添加int (*myFuncPtr)(int a, int b);

typedef

在上面typedef int (*myFuncPtrType)(int a, int b); 的声明中,编译器为变量分配了一些内存。但是,如果我们在不同的编译单元中编写一些代码,并且想要引用myInt,我们需要将它声明为myInt(在引用编译单元中),以便我们引用相同的内存。如果没有extern,编译器将分配第二个extern,这将导致链接器错误(实际上这不是真的,因为C允许暂定定义,您不应该使用它)。

如上所述,函数不是正常值,并且始终隐式myInt。但是,函数指针正常值,如果您尝试从单独的编译单元引用全局函数指针,则需要extern

通常,您可以将全局变量(和函数)的extern放入头文件中。然后,您将标题包含在包含这些变量和函数的定义的编译单元中,以便编译器可以确保类型匹配。

答案 1 :(得分:1)

变量定义或声明的语法是一个类型,后跟一个或多个可能带有修饰符的变量。所以一些例子是:

 int  a, b;    // two int variables a and b
 int  *a, b;   // pointer to an int variable a and an int variable b
 int  a, *b, **c;   // int variable a, pointer to an int variable b, and pointer to a pointer to an int variable c

请注意,在所有这些中,星号会修改星号右侧的变量,将其从int更改为指向int的指针或指向int的指针。所以这些可能会被用作:

int a, *b, **c, d;

a = 5;     // set a == 5
b = &a;    // set b == address of a
c = &b;    // set c == address of b which in this case has the address of int variable a

d = **c;   // put value of a into d using pointer to point to an int variable a
d = *b;    // put value of a into d using pointer to an int variable a
d = a;     // put value of a into d using the variable a directly

extern语句用于指示变量的定义位于某个其他文件中,并且该变量具有全局可见性。因此,您可以使用extern关键字声明一个变量,以明确变量,以便C编译器具有在编译时进行良好检查所需的信息。 extern表示变量实际上是使用其内存分配定义的,而不是使用变量的源所在的文件。

由于函数指针是一个变量,如果你有一个函数指针需要全局可见性,当你为实际定义它的源文件以及分配的内存以外的文件声明变量时,你会使用{{1} } keyword作为函数指针变量声明的一部分。但是,如果它是在函数内的堆栈上分配的变量,或者如果在extern内使用它来创建struct的成员,那么您将不会使用struct修饰符关于变量。

typedef是现代C的一个非常好的功能,因为它允许您创建一个等同于一种中途新类型的别名。要拥有创建新类型的全部功能,确实需要C ++的类类型功能,它允许为新类型定义运算符。但是,typedef确实提供了一种允许程序员为类型创建别名的好方法。

多年前,在将extern添加到C标准之前的旧C编译器中,人们通常会使用C预处理器来定义宏来为复杂类型创建别名。 typedef是一种更清洁的方式!

typedef的大多数用法是提供一种方法,使其更简洁,更清晰地编写变量定义。由于这个原因,它与typedef定义一起使用很多。因此,您可能有struct定义,如下所示:

struct

这将为typdef struct { int iA; int iB; } MyStruct, *PMyStruct; 创建两个新别名,一个用于struct本身,另一个用于指向struct的指针,这些可能会像以下一样使用:

struct

此示例具有MyStruct exampleStruct; PMyStruct pExampleStrut; pExampleStruct = &exampleStruct; 关键字的基本结构,根据现有类型定义新类型,以及新类型的名称。

typedef添加到C标准之前,您将为结构指定一个标记,结果将是如下所示的代码:

typedef

在哪个指针处,人们通常会添加一个C预处理器定义,以便更容易编写,如下所示:

struct myTagStruct {     // create a struct declaration with the tag of myTagStruct
    int a;
    int b;
};

struct myTagStruct myStruct;      // create a variable myStruct of the struct

然后使用它:

#define MYTAGSTRUCT  struct myTagStruct

然而,函数指针的MYTAGSTRUCT myStruct; 语法略有不同。它更像是函数声明,其中函数的名称是typedef的实际名称或实际使用的别名。函数指针的typedef的基本结构是typedef关键字,后跟与函数原型相同的语法,其中函数的名称用作typedef名称。

typedef

括号内容很重要,因为它们强制编译器以特定方式解释源文本。 C编译器具有用于解析源文本的规则,您可以通过使用括号来更改C编译器解释源文本的方式。规则与解析以及C编译器如何定位变量名称有关,然后通过使用关于左右关联性的规则来确定变量的类型。

typedef int (*pFunc)(int a1, int b1);

函数原型不是函数指针变量。 a = 5 * b + 1; // 5 times b then add 1 a = 5 * (b + 1); // 5 times the sum of b and 1 int *pFunc(int a1, int b1); // function prototype for function pFunc which returns a pointer to an int int **pFunct(int a1, int b1); // function prototype for function pFunc which returns a pointer to a pointer to an int int (*pfunc)(int a1, int b1); // function pointer variable for pointer to a function which returns an int int *(*pFunc)(int a1, int b1); // function pointer variable for pointer to a function which returns a pointer to an int 的语法类似于未使用typedef的变量定义的语法。

typedef

那么有一个指向函数的指针是什么意思?函数入口点具有地址,因为函数是位于特定存储区域的机器代码。函数的地址是函数机器代码的执行应该开始的地方。

编译C源代码时,函数调用被转换为一系列机器指令,跳转到函数的地址。实际的机器指令实际上并不是跳转,而是一个调用指令,它在跳转之前保存返回地址,这样当被调用函数完成时它可以返回到调用它的位置。

函数指针变量用作函数语句。两者之间的差异类似于数组变量和指针变量之间的差异。大多数C编译器将数组变量视为指向变量的常量指针。函数名被视为大多数C编译器对函数的常量指针。

函数指针给你带来的是灵活性,虽然它具有灵活性,因为任何强大的力量也会导致巨大的毁灭。

函数指针变量的一个用途是将函数地址作为参数传递给另一个函数。例如,C标准库有几个排序函数,它们需要一个排序函数的参数来比较被排序的两个元素。另一个例子是一个线程库,当你创建一个线程时,你指定要作为一个线程执行的函数的地址。

另一种情况是提供某种隐藏实现细节的界面。让我们假设您有一个打印功能,您想要用于几个不同的输出接收器或输出的位置,比如文件,打印机和终端窗口。这在本质上类似于C ++编译器如何实现虚函数或COM对象如何通过COM接口实现。所以你可以做类似下面的事情,这是一个非常简单的例子,缺少细节:

typedef  int * pInt;    // create typedef for pointer to an int
int *a;                 // create a variable that is a pointer to an int
pInt b;                 // create a variable that is a pointer to an int
typedef int (*pIntFunc)(int a1, int b1);    // create typedef for pointer to a function
int (*pFuncA)(int a1, int b1);           // create a variable pFuncA that is a pointer to a function
pIntFunc  pFuncB;                        // create a variable pFuncB that is a pointer to a function

答案 2 :(得分:1)

为了明确其他人给出的解释,在C / C ++中,括号是右关联的,因此以下声明:

typedef int *pFunc(int, int);

相当于:

typedef int *(pFunc(int, int));

这将是函数的声明原型,返回指向整数的指针,而不是指向返回整数的函数的指针的声明。

这就是为什么你需要在(*pFunc)周围编写括号来打破正确的关联,并告诉编译器pFunc是一个指向函数的指针,而不仅仅是一个函数。