Linker warning LNK4221, and some tips to avoid it
Hello, my name is Chandler Shen, a developer from Visual C++ Shanghai team.
There is some confusion about warning LNK4221 and whether it is safe to ignore it. I would like to give an explanation on when you may encounter this warning and provide tips for some typical scenarios and workarounds.
What does LNK4221 mean?
When building static/import libraries, linker might emit the following message:
“warning LNK4221: no public symbols found; archive member will be inaccessible” (The message will be improved in VS2010 to ”warning LNK4221: This object file does not define any previously undefined public symbols, so it will not be used by any link operation that consumes this library”).
Just as the message says, this warning occurs when an object file was added to an archive library without any previously undefined public symbols, so it will not be accessible in subsequent linker commands.
Though this warning can be safely ignored sometimes, user should be aware of what’s happening beneath the surface.
Create two source files, a.cpp and b.cpp, with following contents
In ” Visual Studio 2008 Command Prompt”, enter the following commands (Note: We use command line here to specify the order of .obj files; Visual Studio 2008 will supply linker with .obj files in alphabetical order)
1. cl /c a.cpp b.cpp
2. link /lib /out:test.lib a.obj b.obj
And LNK4221 will be thrown for a.obj as the following.
a.obj : warning LNK4221: no public symbols found; archive member will be inaccessible
For the above case, atlbase.h (shipped with Visual Studio) contains some definitions of symbols, which will be included in both a.obj and b.obj. Additionally, there is a function, func1, defined in b.obj. Linker will process OBJ files in Last In First Out manner, so when it is processing a.obj, it cannot find any new public symbols in it because b.obj provide all the public symbols that a.obj has, LNK4221 will be thrown. If command line 2 is replaced with following
link /lib /out:test.lib b.obj a.obj
Now the warning is gone!
Typical user scenarios
In practice, most LNK4221 warnings occur on stdafx.obj, which is the name of the precompiled header. In this section, we will focus on two typical “real-world” scenarios and provide corresponding workarounds.
Build a static library with object files
Generally, LNK4221 should not be thrown on stdafx.obj. But in some cases, such as migrating from other IDEs or project systems, there are more than one source files which are specified with /Yc, hence the .obj files of them will all contain the content from stdafx.obj, if any of them is processed prior to stdafx.obj, a LNK4221 will be thrown. If this occurs, try following steps to check your configuration of Precompiled Headers.
1. In the Property Dialog of the project, select “Configuration Properties”->”C/C++”->”Precompiled Headers”. Select “Use Precompiled Header (/Yu)” for “Create/Use Precompiled Header”, and accept the default values for the other two options.
2. Right click “stdafx.cpp” in solution explorer. Select “Properties”, then select “Configuration Properties” -> ”C/C++” -> “Precompiled Headers”. Select “Create Precompiled Header (/Yc)” for “Create/Use Precompiled Header”, and accept the default values for other two options.
3. Save the changes and rebuild the project. If the problem remains, check the build log to confirm that only one source file (“stdafx.cpp” here) is compiled with /Yc and the others are compiled with /Yu.
Build a static library with other static libraries
Sometimes, the user would like to build a single static library from existing libraries for convenience. This approach might “lead to” multiple LNK4221 warnings, especially when the user changed the settings of output path of these libraries. To begin with, create a simple test case as follows
1. In Visual Studio 2008, create a “Blank Solution”, and name it “solution1”.
2. Add three new “Win32 Projects”, lib1, lib2 and wrapperlib, into solution1, and in the “Application Settings” page of the wizard, select “Static library” for “Application type”, check “Precompiled header” for “Additional options”, and check “MFC” for “Add common header files for”.
3. Add a new source file, lib1.cpp, into project lib1 with following code
4. Add a new source file, lib2.cpp, into project lib2 with following code
5. In the Property Dialog of wrapperlib, select “Configuration Properties” -> “Librarian” -> “General”, enter “lib1.lib lib2.lib” in “Additional Dependencies”, and enter “$(OutDir)” in “Additional Library Directories”.
After all projects are created, build all libraries in the order of lib1, lib2 and wrapperlib.lib, everything should go well except an informative message as following will be shown:
The reason why you see the message is because the linker builds a library from some object files and static libraries. It will attempt to replace archive members of input libraries with those in the command line with the same name. In this case, because “.\Debug\stdafx.obj” was used as the name of the corresponding archive member of stdafx.obj for all three libraries. When linker is processing the “.\Debug\stdafx.obj” in command line (the command line used to build wrapperlib.lib would look like “link /lib /out:wrapperlib.lib .\Debug\stdafx.obj lib1.lib lib2.lib”), it finds that both lib1 and lib2 contain a member with same name, so linker will exclude these two from linking. Therefore, no other files will contain symbols defined in this “stdafx.obj”.
To verify the above explanation, perform the following steps:
1. In the Property Dialog of lib1, select “Configuration Properties” -> “General”, set “Intermediate Directory” with “$(SolutionDir)/intermediate/$(ConfigurationName)/$(ProjectName)”, which is a common style of project hierarchy for some users.
2. Rebuild lib1 and wrapperlib, and following warning will be shown.
stdafx.obj : warning LNK4221: no public symbols found; archive member will be inaccessible.
Is it (both the replacement mechanism and the LNK4221) a bug of linker? The answer is no, because:
1. Linker should be very cautious about not dropping any useful object in a library just because its name conflicts with that of another object
2. If something unusual happens (an object contributes no new symbols to archive), user should be informed.
Since this warning is widely reported , the following workaround is recommended
Perform following steps to replace “stdafx.obj” in input libraries manually.
1. In “Visual Studio 2008 Command Prompt”, enter the following command for each input libraries.
lib /remove:.\Debug\stdafx.obj lib1.lib
The name after “/remove” depends on your actual settings, you can use the following commad to confirm
dumpbin /archivemembers lib1.lib
2. If some of “stdafx.obj” are not identical in content, you should merge all “stdafx.obj” into the one in wrapperlib.lib beforehand.
a. Delete all contents in the stdafx.h from wrapperlib.lib
b. Inspect all stdafx.h from input libraries (lib1.lib, lib2.lib, etc.), if there is any content (such as an “#include” clause) that is not included in the one from wrapperlib.lib, add it. If there are conflicting definitions, it’s up to you which one is to be used.
c. Repeat step b until all stdafx.h from input libraries are inspected, so the one from wrapperlib.lib will contain all contents.
3. Build wrapperlib.lib
If you don’t want to work around this linker warning, just ignore LNK4221. However, you should be sure that all “stdafx.obj” are identical in content.