簡體   English   中英

關於C ++函數指針的困惑

[英]Confusion about C++ function pointer

我的源文件名以cpp結尾。

以下是2個聲明:

void (*f1)(void);

void *f2(void);

我認為:

  • f1是有效的函數指針。
  • f2是一個返回void *指針的函數。

然后我有另一個功能:

void f3(void *p_func);

我認為:

  • 盡管參數名稱為p_funcf3還是一個帶有void *指針的函數,即32位計算機上的32位無符號整數。

以下2條語句:

f3(&f1); //<----------It works
f3(&f2); //<----------Compiler error

編譯器錯誤是:

no known conversion from 'void *(*)()' to 'void *' for 2nd argument;

我的問題:

  • &f1應該是函數指針的地址,為什么可以將它作為void *傳遞給f3
  • 為什么&f2void *(*)()
  • void *(*)()到底是什么意思?

您說對了,第一個是指向函數的指針,第二個是指向函數的聲明。

這意味着編譯器在警告方面也是正確的。

第一次調用( f3(&f1) )傳遞函數指針的地址,該地址可轉換為void * (與我的先前評論相反)。 因此,沒有錯誤要求。 (它是一個指向函數指針的指針,因此是一個數據對象,而不是一個函數指針。忽略&會得到與第二個調用相同的錯誤。)

第二次調用( f3(&f2) )將指針傳遞給函數,並且函數指針和void指針不可相互轉換。 函數名稱前面的&是多余的,並且在上下文中會引起誤導。 您可以在函數名稱中添加任意數量的*或單個& (或全部省略),並且將其視為相同-標准C的怪異方面之一。(另請參見為什么函數指針定義可與任何函數一起使用&號或星號'*'的數量?

我注意到我必須使用-pedantic來使GCC -pedantic有所抱怨。 這是該標准在附件J中記錄了通用擴展的結果:

J.5.7函數指針轉換

¶1指向對象或void的指針可以轉換為指向函數的指針,從而允許將數據作為函數調用(6.5.4)。

¶2指向函數的指針可以強制轉換為指向對象的指針,也可以轉換為void ,從而可以檢查或修改函數(例如,通過調試器)(6.5.4)。

您問void *(*)()意味着什么。 這是指向函數的指針的“ cast”形式,該函數采用不確定的參數列表(但不包括可變參數的結尾帶有省略號... )並返回指向函數的指針。


小伙子

您能否從C標准添加有關函數指針的引用,並且void指針不可相互轉換?

是的-很簡單:

6.2.5類型

¶1…類型分為對象類型(描述對象的類型)和函數類型(描述函數的類型)。

6.3轉換

6.3.2.3指針

¶1指向void的指針可以與任何對象類型的指針進行轉換。 指向任何對象類型的指針都可以轉換為void指針,然后再返回; 結果應等於原始指針。

¶7指向對象類型的指針可以轉換為指向不同對象類型的指針。 如果對於所引用的類型,結果指針未正確對齊68) ,則該行為未定義。 否則,當再次轉換回時,結果應等於原始指針。 當指向對象的指針轉換為指向字符類型的指針時,結果指向該對象的最低尋址字節。 結果的連續遞增(直到對象的大小)會產生指向對象剩余字節的指針。

¶8指向一種類型的函數的指針可以轉換為指向另一種類型的函數的指針,然后再返回。 結果應等於原始指針。 如果使用轉換后的指針來調用其類型與引用的類型不兼容的函數,則該行為是不確定的。

這些是指針類型之間唯一定義的轉換。 沒有關於在對象的指針和函數的指針之間進行轉換的信息,因此是不允許的(但不一定需要診斷;它不在標准的“約束”部分中)。

經過測試的代碼

變體1( pf19.c ):

void (*f1)(void);
void *f2(void);

void f3(void *p_func);

int main(void)
{
    f3(&f1);
    f3(&f2);
}

編譯警告(由於-Werror-pedantic-pedantic錯誤):

$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes  -Wold-style-definition -pedantic -c pf19.c
pf19.c: In function ‘main’:
pf19.c:9:12: error: ISO C forbids passing argument 1 of ‘f3’ between function pointer and ‘void *’ [-Werror=pedantic]
         f3(&f2); //<----------Compiler error
            ^
pf19.c:4:6: note: expected ‘void *’ but argument is of type ‘void * (*)(void)’
 void f3(void *p_func);
      ^~
cc1: all warnings being treated as errors

變體2(也為pf19.c ):

void (*f1)(void);
void *f2(void);

void f3(void *p_func);

int main(void)
{
    f3(f1);
    f3(&f2);
}

編譯消息:

$ gcc -O3   -g      -std=c11   -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes  -Wold-style-definition      -pedantic -c pf19.c
pf19.c: In function ‘main’:
pf19.c:8:8: error: ISO C forbids passing argument 1 of ‘f3’ between function pointer and ‘void *’ [-Werror=pedantic]
     f3(f1);
        ^~
pf19.c:4:6: note: expected ‘void *’ but argument is of type ‘void (*)(void)’
 void f3(void *p_func);
      ^~
pf19.c:9:8: error: ISO C forbids passing argument 1 of ‘f3’ between function pointer and ‘void *’ [-Werror=pedantic]
     f3(&f2);
        ^
pf19.c:4:6: note: expected ‘void *’ but argument is of type ‘void * (*)(void)’
 void f3(void *p_func);
      ^~
cc1: all warnings being treated as errors
$

該消息的措辭是從C編譯器不同用C ++編譯器相比,但意圖是相同的( pf17.cc是簡單的復制pf19.c ):

$ g++ -O3 -g -I./inc -std=c++11 -Wall -Wextra -Werror    -c pf17.cc
pf17.cc: In function ‘int main()’:
pf17.cc:8:10: error: invalid conversion from ‘void (*)()’ to ‘void*’ [-fpermissive]
     f3(f1);
          ^
pf17.cc:4:6: note:   initializing argument 1 of ‘void f3(void*)’
 void f3(void *p_func);
      ^~
pf17.cc:9:8: error: invalid conversion from ‘void* (*)()’ to ‘void*’ [-fpermissive]
     f3(&f2);
        ^~~
pf17.cc:4:6: note:   initializing argument 1 of ‘void f3(void*)’
 void f3(void *p_func);
      ^~
$

測試:Mac OS X 10.11.6 El Capitan上的GCC 6.2.0。


感謝Dmitri 注意到 §6.3.2.3¶1是相關的,以及附件J.5.7。

如果這是程序,則錯誤消息沒有任何意義。 因為在每個指針都可以轉換為void * ,而無需void *轉換即可返回。 您的編譯器似乎認為無法從指針轉換為void * ,這是工作方式。

盡管對您的問題的評論也是正確的,但如果將其編譯為代碼,則在兩種情況下都應編譯您的代碼。 標准嚴格禁止這種轉換,但是錯誤消息仍然是c ++錯誤而不是c編譯器錯誤,並且從/向void * / function指針的轉換是常見的擴展

J.5.7函數指針轉換

  1. 指向對象或void的指針可以轉換為指向函數的指針,從而允許將數據作為函數調用(6.5.4)。
  2. 可以將指向函數的指針轉換為指向對象的指針,也可以將其轉換為void,以允許對函數進行檢查或修改(例如,通過調試器)(6.5.4)。

盡管不能保證所有系統都支持此功能,但大多數系統都支持。

編譯為可能原因是

  1. 您直接調用了c ++編譯器,例如g++
  2. 您用.cpp擴展名而不是.c命名源文件

如果它是源代碼,則該錯誤是有道理的,因為不允許進行此類轉換,但是在c 中,即使嚴格禁止這樣做,也可能會正確編譯( 並且大多數時候會 )。

還要注意, 中的函數指針不是執行AFAIK的常用方法。 因為當您有一個對象時,您只能創建指向靜態成員的指針,這與具有指向普通函數的指針沒有太大區別。 回調並不常見,實際上,我廣泛使用了Qt的庫( 在c ++ 11標准之前 )需要一個名為moc的工具,以使“ 回調 ”起作用。 該工具從頭文件生成代碼,以允許回調或( 對它們的模擬 )在工作。

  • &f1應該是函數指針的地址,為什么它可以作為void *傳遞給f3?

這是正確的,這也正是為什么它成功。 因為指向指針的指針可以隱式轉換為void * ,而不管其目標指向哪種數據類型。

  • 為什么&f2是void *(*)() void *(*)()到底是什么意思?

您可能會誤解函數指針符號。 void *(*)()是函數指針的表示法。 void *f2(void)是一個返回void指針的函數。 &f2是一個函數指針(指向該函數)。 編譯器告訴您不能將函數指針隱式轉換為void * (根據C標准,不能保證這樣做,並且您的編譯器顯然不能執行此操作)。

那是獨家新聞。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM