AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 79544030
Accepted
npt
npt
Asked: 2025-03-30 07:39:09 +0800 CST2025-03-30 07:39:09 +0800 CST 2025-03-30 07:39:09 +0800 CST

pergunta sobre namespace anônimo e ligação externa "C"

  • 772

Meu caso de uso é o seguinte para um projeto:

  • Arquivo de cabeçalho AC com algumas funções externas declaradas
  • Um arquivo de origem C++ com essas funções definidas
  • Compilar o código fonte C++ em uma biblioteca compartilhada
  • Em um arquivo de origem C, use as funções declaradas no cabeçalho C vinculando-as ao .so

Estou observando algo estranho enquanto faço do. As coisas começam a funcionar com namepsace sem nome.

Aqui está meu arquivo de exemplo:

c_sample.h:

#include "stddef.h"

extern void hello(void);
extern void bye(void);

cpp_sample.cc:

#include <iostream>
#include "c_sample.h"

extern "C" {
    void hello(void) { std::cout << "HI" << std::endl; }
    void bye(void) { std::cout << "BYE" << std::endl; }
}

Ao tentar construir uma biblioteca de compartilhamento, vejo o erro que é esperado, pois c_sample.h está incluído fora do extern "C"bloco.

g++ cpp_sample.cc -shared -o libcppsample.so     
cpp_sample.cc:5:7: error: declaration of 'hello' has a different language linkage
    5 |         void hello() { std::cout << "HI" << std::endl;}
      |              ^
./c_sample.h:3:13: note: previous declaration is here
    3 | extern void hello();
      |             ^
cpp_sample.cc:6:7: error: declaration of 'bye' has a different language linkage
    6 |         void bye() { std::cout << "BYE" << std::endl;}
      |              ^
./c_sample.h:4:13: note: previous declaration is here
    4 | extern void bye();
      |             ^
2 errors generated.

No entanto, a mágica acontece no momento em que envolvo isso em um namespace sem nome

cpp_sample.cc:

#include <iostream>
#include "c_sample.h"

extern "C" {
namespace {
    void hello(void) { std::cout << "HI" << std::endl; }
    void bye(void) { std::cout << "BYE" << std::endl; }
}
}

Isso foi compilado. E quando tentei usá-lo de outro arquivo de origem C, ele até funcionou

#include "stdio.h"
#include "c_sample.h"

int main() {
    hello();
}
$ gcc another.c -L/tmp -lcppsample -o another
$ ./another
HI

Como isso funciona apenas envolvendo-o dentro de um namespace? Como ele é capaz de vincular as funções declaradas com suas definições?

c++
  • 2 2 respostas
  • 103 Views

2 respostas

  • Voted
  1. Mike Kinghan
    2025-03-30T19:13:22+08:002025-03-30T19:13:22+08:00

    Como isso funciona apenas envolvendo-o dentro de um namespace? Como ele é capaz de vincular as funções declaradas com suas definições?

    Aqui está uma reprodução trivialmente diferente do seu caso de compilação com falha:

    $ tail -n +1 c_sample.h bad.cc 
    ==> c_sample.h <==
    #ifndef C_SAMPLE_H
    #define C_SAMPLE_H
    
    extern void hello(void);
    extern void bye(void);
    
    #endif
    
    ==> bad.cc <==
    #include <iostream>
    #include "c_sample.h"
    
    extern "C" {
        void hello(void) { std::cout << "HI" << std::endl; }
        void bye(void) { std::cout << "BYE" << std::endl; }
    }
    

    Eu apenas removi #include "stddef.h"de c_sample.h, pois ele não contribui em nada, e a falha de compilação é o que você espera e entende:

    $ g++ --version | head -n1
    g++ (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
    
    $ g++ -std=c++11 -c bad.cc -Wall -Wextra -pedantic
    bad.cc:5:10: error: conflicting declaration of ‘void hello()’ with ‘C’ linkage
        5 |     void hello(void) { std::cout << "HI" << std::endl; }
          |          ^~~~~
    In file included from bad.cc:2:
    c_sample.h:4:13: note: previous declaration with ‘C++’ linkage
        4 | extern void hello(void);
          |             ^~~~~
    rbad.cc:6:10: error: conflicting declaration of ‘void bye()’ with ‘C’ linkage
        6 |     void bye(void) { std::cout << "BYE" << std::endl; }
          |          ^~~
    c_sample.h:5:13: note: previous declaration with ‘C++’ linkage
        5 | extern void bye(void);
          |             ^~~
    

    Agora aqui está um código fonte C++ ligeiramente diferente da sua cpp_sample.ccversão #2 compilada com sucesso.

    $ cat good.cc
    #include <iostream>
    #include "c_sample.h"
    
    extern "C" {
    namespace {
        void hello(void) { std::cout << "HI" << std::endl; }
    
    }
    namespace ns {
        void bye(void) { std::cout << "BYE" << std::endl; }
    }
    }
    

    A diferença aqui é que eu defini byeem um namespace diferente ( ns), do namespace ( <anonymous>) que contém a definição de hello. Isso é só para eliminar qualquer diferença que faça. Mas não faz nenhuma:

    $ g++ -std=c++11 -c good.cc -Wall -Wextra -pedantic; echo Done
    Done
    

    Ele compila limpo. A tabela de símbolos do arquivo de objeto de saída é:

    $ readelf -sW good.o
    
    Symbol table '.symtab' contains 15 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS good.cc
         2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 .text
         3: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 .rodata
         4: 0000000000000007     1 OBJECT  LOCAL  DEFAULT    5 _ZNSt8__detail30__integer_to_chars_is_unsignedIjEE
         5: 0000000000000008     1 OBJECT  LOCAL  DEFAULT    5 _ZNSt8__detail30__integer_to_chars_is_unsignedImEE
         6: 0000000000000009     1 OBJECT  LOCAL  DEFAULT    5 _ZNSt8__detail30__integer_to_chars_is_unsignedIyEE
         7: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _ZSt21ios_base_library_initv
         8: 0000000000000000    54 FUNC    GLOBAL DEFAULT    1 hello
         9: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _ZSt4cout
        10: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
        11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_
        12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
        13: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _ZNSolsEPFRSoS_E
        14: 0000000000000036    54 FUNC    GLOBAL DEFAULT    1 bye
        
    

    onde podemos ver que os nomes das funções de origem void <anonymous>::hello(void)e void ns::bye(void), foram traduzidos para símbolos de linker globais definidos helloe byesem a manipulação de nomes em C++, exatamente como exigido pelo extern "C"escopo em que são declarados e definidos: nenhum namespace e nenhuma assinatura de chamada são codificados nos símbolos de linker. Eles são apenas símbolos C antigos e simples, ao contrário de, digamos:

    $ c++filt _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
    

    E assim:

    $ cat main.c
    #include "c_sample.h"
    
    int main() {
        hello();
        bye();
        return 0;
    }
    
    $ gcc main.c good.o -lstdc++
    $ ./a.out
    HI
    BYE
    

    está bem.

    extern "C"não altera a análise sintática do compilador da gramática C++. Ao incluir as definições de função de helloe byeem um namespace ou namespaces diferentes, os identificadores são distinguidos das declarações incluídas de c_sample.h, como sempre seriam. Mas extern "C"suprime a confusão de nomes C++ dos símbolos que são emitidos no código do objeto para os identificadores incluídos no namespace em favor da simbolização C. Então, os namespaces são perdidos, e as assinaturas de chamada. Tudo o que importa para o vinculador é que ele possa encontrar definições dos símbolos referenciados nos arquivos de objeto e bibliotecas que são de entrada, hello, bye, _ZSt4coutetc.

    Podemos enfatizar essa distinção com:

    $ cat bad_1.cc
    #include <iostream>
    #include "c_sample.h"
    
    extern "C" {
    namespace {
        void hello(void) { std::cout << "HI" << std::endl; }
    
    }
    namespace ns {
        void bye(void) { std::cout << "BYE" << std::endl; }
    }
    }
    
    namespace {
        void hello(void) { std::cout << "HI" << std::endl; }
    
    }
    namespace ns {
        void bye(void) { std::cout << "BYE" << std::endl; }
    }
    

    onde void <anonymous>::hello(void)e void ns::bye(void)são definidos duas vezes, uma vez dentro do escopo extern "C"e outra não.

    $ g++ -std=c++11 -c bad_1.cc
    bad_1.cc:15:10: error: redefinition of ‘void {anonymous}::hello()’
       15 |     void hello(void) { std::cout << "HI" << std::endl; }
          |          ^~~~~
    bad_1.cc:6:10: note: ‘void {anonymous}::hello()’ previously defined here
        6 |     void hello(void) { std::cout << "HI" << std::endl; }
          |          ^~~~~
    bad_1.cc:19:10: error: redefinition of ‘void ns::bye()’
       19 |     void bye(void) { std::cout << "BYE" << std::endl; }
          |          ^~~
    bad_1.cc:10:10: note: ‘void ns::bye()’ previously defined here
       10 |     void bye(void) { std::cout << "BYE" << std::endl; }
          |          ^~~
          
    

    O extern "C"escopo não faz diferença para a análise sintática de C++: as múltiplas definições são C++ ilegais. Não chegamos nem a emitir símbolos, C++-mangled ou não. Mas se eles não fossem ilegais e chegássemos tão longe, os símbolos emitidos do escopo de ligação C++ padrão seriam:

    FUNC    LOCAL  DEFAULT    1 _ZN12_GLOBAL__N_15helloEv
    FUNC    GLOBAL DEFAULT    1 _ZN2ns3byeEv
    

    e os emitidos pelo extern "C"osciloscópio seriam como antes:

    FUNC    GLOBAL DEFAULT    1 hello
    FUNC    GLOBAL DEFAULT    1 bye
    

    sem múltiplas definições no momento do link.

    Você pode ver que good.ccé equivalente a:

    $ cat good_1.cc
    #include <iostream>
    
    extern void hello(void);
    extern void bye(void);
    
    extern "C" {
    namespace {
        void hello(void) { std::cout << "HI" << std::endl; }
    
    }
    namespace ns {
        void bye(void) { std::cout << "BYE" << std::endl; }
    }
    }
    

    onde é óbvio que as declarações:

    extern void hello(void);
    extern void bye(void);
    

    com ligação C++ por padrão são simplesmente não relacionados às definições incluídas no namespace e são redundantes. Assim, com outro arquivo de origem C++:

    $ cat hellobye.cc 
    #include "c_sample.h"
    
    void hello(void) { std::cout << "Hello" << std::endl; }
    
    void bye(void) { std::cout << "Goodbye" << std::endl; }
    

    podemos compilar e vincular outro programa:

    $ g++ -std=c++11 -c main.c
    $ g++ -std=c++11 -c hellobye.cc  
    $ g++ main.o good.o hellobye.o  
    $ ./a.out
    Hello
    Goodbye
    

    em que não são redundantes. Aqui, compilar com g++, main.c é compilado como fonte C++ (isso me poupa de copiar o mesmo arquivo com .ccextensão) e as declarações de c_sample.htêm ligação C++ por padrão: ou seja, elas produzirão símbolos C++-mangled. Então vemos que:

    $ readelf -sW main.o
    
    Symbol table '.symtab' contains 6 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS main.c
         2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 .text
         3: 0000000000000000    25 FUNC    GLOBAL DEFAULT    1 main
         4: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z5hellov
         5: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z3byev
         
    

    main.ofaz referências externas aos símbolos C++-mangled _Z5hellove _Z3byev, que são desmangled:

    $ readelf -sCW main.o | egrep \(hello\|bye\) 
         4: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND hello()
         5: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND bye()
         
    

    e são definidos em:

    $ readelf -sW hellobye.o | egrep \(hello\|bye\) 
         1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS hellobye.cc
         8: 0000000000000000    54 FUNC    GLOBAL DEFAULT    1 _Z5hellov
        14: 0000000000000036    54 FUNC    GLOBAL DEFAULT    1 _Z3byev
        
    

    A tabela de símbolos deste programa:

     readelf -sW a.out | egrep \(hello\|bye\)
        14: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS hellobye.cc
        28: 0000000000001224    54 FUNC    GLOBAL DEFAULT   16 _Z3byev
        43: 0000000000001182    54 FUNC    GLOBAL DEFAULT   16 hello
        44: 00000000000011ee    54 FUNC    GLOBAL DEFAULT   16 _Z5hellov
        45: 00000000000011b8    54 FUNC    GLOBAL DEFAULT   16 bye
    

    tem os extern "C"símbolos vinculados de good.oe os símbolos C++ vinculados de hellobye.o, mas os símbolos de good.osão redundantes:

    $ g++ main.o hellobye.o 
    $ ./a.out
    Hello
    Goodbye
    

    Da mesma forma para o programa:

    $ gcc -c main.c
    $ gcc main.o good.o hellobye.o -lstdc++
    $ ./a.out
    HI
    BYE
    
    $ readelf -sW a.out | egrep \(hello\|bye\)
        14: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS hellobye.cc
        28: 0000000000001224    54 FUNC    GLOBAL DEFAULT   16 _Z3byev
        43: 0000000000001182    54 FUNC    GLOBAL DEFAULT   16 hello
        44: 00000000000011ee    54 FUNC    GLOBAL DEFAULT   16 _Z5hellov
        45: 00000000000011b8    54 FUNC    GLOBAL DEFAULT   16 bye
        
    

    onde os símbolos hellobye.osão redundantes.

    Você pergunta especificamente sobre g++ -std=c++11, mas você fará as mesmas descobertas com recente clang/clang++ou MSVC, padrão padrão. Ao contrário de g++, clang++avisa quando um *.carquivo é inserido, mas como g++compila como fonte C++. MSVC requer a opção /TPde fazê-lo compilar um *.carquivo como C++.


    O que você queria alcançar era escrever uma biblioteca compartilhada em C++ que tivesse uma API C. Veja como você faria isso:

    $ tail -n +1 hb.h hb.cc 
    ==> hb.h <==
    #ifndef HB_H
    #define HB_H
    
    #ifdef __cplusplus 
    extern "C" {
    #endif
    
    extern void hello(void);
    extern void bye(void);
    
    #ifdef __cplusplus
    }
    #endif
    
    
    #endif
    
    ==> hb.cc <==
    #include "hb.h"
    #include <iostream>
    
    extern "C" {
    
    void hello(void) { std::cout << "Hello" << std::endl; }
    
    void bye(void) { std::cout << "Goodbye" << std::endl; }
    
    }
    

    Crie a biblioteca

    $ g++ -fPIC -shared hb.cc -o libhb.so
    

    Vincule-o a um programa em C:

    $ cat main_1.c
    #include "hb.h"
    
    int main() {
        hello();
        bye();
        return 0;
    }
    
    
    $ gcc main_1.c -L. -lhb -Wl,-rpath='$ORIGIN'
    $ ./a.out
    Hello
    Goodbye
    

    Ou vincule-o a um programa C++, onde a API C será ativada por meio do arquivo de cabeçalho:

    $ g++ main_1.c -L. -lhb -Wl,-rpath='$ORIGIN'
    $ ./a.out
    Hello
    Goodbye
    
    • 1
  2. Best Answer
    n. m. could be an AI
    2025-03-30T14:17:20+08:002025-03-30T14:17:20+08:00

    Quando você #include "c_sample.h"está em um arquivo C++, você declara algumas funções com vinculação C++ .

    Quando você declara funções manualmente no namespace global, você obtém um conflito. Uma função com ligação C não pode ter o mesmo nome que uma função com ligação C++ no mesmo namespace.

    Quando você declara funções manualmente no namespace sem nome, não há conflito. Você pode declarar uma função em qualquer namespace e uma função com o mesmo nome em um namespace diferente, independentemente da vinculação.

    A função de ligação AC declarada em qualquer namespace é apenas uma função C comum que pode ser implementada em um arquivo de origem C e compilada por um compilador C (se nenhum tipo específico de C++ estiver envolvido).

    Não há nada indefinido ou específico de implementação aqui, mas o código é frágil e não muito útil. A coisa certa a fazer é adicionar contitionally-compiled extern "C"bem no cabeçalho C.

    • 0

relate perguntas

  • Por que os compiladores perdem a vetorização aqui?

  • Erro de compilação usando CMake com biblioteca [fechada]

  • Erro lançado toda vez que tento executar o premake

  • Como criar um tipo de octeto semelhante a std::byte em C++?

  • Somente operações bit a bit para std::byte em C++ 17?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Reformatar números, inserindo separadores em posições fixas

    • 6 respostas
  • Marko Smith

    Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não?

    • 2 respostas
  • Marko Smith

    Problema com extensão desinstalada automaticamente do VScode (tema Material)

    • 2 respostas
  • Marko Smith

    Vue 3: Erro na criação "Identificador esperado, mas encontrado 'import'" [duplicado]

    • 1 respostas
  • Marko Smith

    Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores?

    • 1 respostas
  • Marko Smith

    Como faço para corrigir um erro MODULE_NOT_FOUND para um módulo que não importei manualmente?

    • 6 respostas
  • Marko Smith

    `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso?

    • 3 respostas
  • Marko Smith

    Um programa vazio que não faz nada em C++ precisa de um heap de 204 KB, mas não em C

    • 1 respostas
  • Marko Smith

    PowerBI atualmente quebrado com BigQuery: problema de driver Simba com atualização do Windows

    • 2 respostas
  • Marko Smith

    AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos

    • 1 respostas
  • Martin Hope
    Fantastic Mr Fox Somente o tipo copiável não é aceito na implementação std::vector do MSVC 2025-04-23 06:40:49 +0800 CST
  • Martin Hope
    Howard Hinnant Encontre o próximo dia da semana usando o cronógrafo 2025-04-21 08:30:25 +0800 CST
  • Martin Hope
    Fedor O inicializador de membro do construtor pode incluir a inicialização de outro membro? 2025-04-15 01:01:44 +0800 CST
  • Martin Hope
    Petr Filipský Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não? 2025-03-23 21:39:40 +0800 CST
  • Martin Hope
    Catskul O C++20 mudou para permitir a conversão de `type(&)[N]` de matriz de limites conhecidos para `type(&)[]` de matriz de limites desconhecidos? 2025-03-04 06:57:53 +0800 CST
  • Martin Hope
    Stefan Pochmann Como/por que {2,3,10} e {x,3,10} com x=2 são ordenados de forma diferente? 2025-01-13 23:24:07 +0800 CST
  • Martin Hope
    Chad Feller O ponto e vírgula agora é opcional em condicionais bash com [[ .. ]] na versão 5.2? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench Por que um traço duplo (--) faz com que esta cláusula MariaDB seja avaliada como verdadeira? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng Por que `dict(id=1, **{'id': 2})` às vezes gera `KeyError: 'id'` em vez de um TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos 2024-03-20 03:12:31 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve