diff --git a/namecollision/.gitignore b/namecollision/.gitignore index f1b57a5..73ab5e1 100644 --- a/namecollision/.gitignore +++ b/namecollision/.gitignore @@ -1,2 +1,6 @@ libhello.so +libhello.dll +libhello.lib main +main.exe +libhello.exp diff --git a/namecollision/README.md b/namecollision/README.md index dd46a63..c17603c 100644 --- a/namecollision/README.md +++ b/namecollision/README.md @@ -6,8 +6,8 @@ `print_obj()`. `main.cpp` defines a `namespace nt` with a free function `print()`, and calls both `nt::print()` and `print_obj()`. -`hello.cpp` is compiled into a shared library (`libhello.so`), and `main.cpp` is -linked against it. +`hello.cpp` is compiled into a shared library (`libhello.so` on Linux, +`libhello.dll` on Windows), and `main.cpp` is linked against it. ## The Problem @@ -80,7 +80,34 @@ This flat namespace behavior means a weak symbol in a `.so` can be silently overridden by any strong symbol of the same name anywhere in the process — even from the main executable. -## The Fix +## Why Windows Is Not Affected + +On Windows, this collision does not occur. The DLL symbol model is fundamentally +different: + +- Only symbols explicitly marked `__declspec(dllexport)` are visible outside the + DLL — everything else is private by default +- The linker generates an import library (`.lib`) with stubs that reference the + DLL by name, so the loader knows exactly which DLL each symbol comes from +- There is no flat global symbol namespace — each DLL is its own isolated scope + +This means the internal `class nt` in `hello.cpp` was never at risk on Windows +regardless of optimization level. The bug is Linux (and ELF) specific. + +To keep `hello.cpp` portable, the `__declspec(dllexport)` on `print_obj` is +wrapped in a macro: + +```cpp +#ifdef _WIN32 +#define EXPORT __declspec(dllexport) +#else +#define EXPORT +#endif +``` + +On Linux `EXPORT` expands to nothing. On Windows it marks the symbol for export. + +## The Linux Fix Wrap internal-use classes in an **anonymous namespace** in `hello.cpp`: @@ -103,6 +130,10 @@ The `_GLOBAL__N_1` prefix is the ABI encoding for the anonymous namespace, making it unique per translation unit and invisible to the dynamic linker. No collision is possible. +The Linux and Windows fixes solve the same problem from opposite defaults: +- Linux: **opt-in to hiding** symbols (anonymous namespace, `-fvisibility=hidden`) +- Windows: **opt-in to exporting** symbols (`__declspec(dllexport)`) + ## Key Takeaways - Class and namespace names mangle identically in the Itanium C++ ABI @@ -110,7 +141,9 @@ collision is possible. the executable - ODR violations are UB with no required diagnostic — bugs may only appear at certain optimization levels +- This is a Linux/ELF-specific problem — Windows DLLs use explicit export tables + and are not affected - Internal implementation details in shared libraries should use anonymous - namespaces to prevent symbol leakage + namespaces to prevent symbol leakage on Linux - Use `objdump -t` or `nm` to inspect symbol visibility and catch these issues early diff --git a/namecollision/build.ps1 b/namecollision/build.ps1 new file mode 100644 index 0000000..c7b789b --- /dev/null +++ b/namecollision/build.ps1 @@ -0,0 +1,15 @@ +param( + [string]$Opt = "-O0" +) + +$ErrorActionPreference = "Stop" + +# Compile hello.cpp as a shared library (also generates libhello.lib import library) +clang++ -std=c++17 $Opt -shared -o libhello.dll hello.cpp "-Wl,/IMPLIB:libhello.lib" +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + +# Compile and link main.cpp with the shared library +clang++ -std=c++17 $Opt -o main.exe main.cpp libhello.lib +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + +Write-Host "Build successful" diff --git a/namecollision/hello.cpp b/namecollision/hello.cpp index 6d0a5b2..efcc288 100644 --- a/namecollision/hello.cpp +++ b/namecollision/hello.cpp @@ -1,5 +1,11 @@ #include +#ifdef _WIN32 +#define EXPORT __declspec(dllexport) +#else +#define EXPORT +#endif + using namespace std; class nt { @@ -9,7 +15,7 @@ public: } }; -void print_obj() { +EXPORT void print_obj() { cout << "Hello from function\n"; nt obj; obj.print();