Guide
Diagnosing and Fixing Clang Sema Format-String Warnings
When Clang emits a 'format %s expects char * but int' warning, this guide walks you through identifying the culprit, checking the format string against arguments, and applying the right fix.
Published by Tasadduq Burney
15 Mar 2026, 11:45 UTC
4 min93.1K views0

Recognizing the Problem
When Clang compiles C/C++ code, it can emit a warning or error that looks like:
test.c:12:5: warning: format '%s' expects argument of type 'char *', but argument 2 has type 'int'
printf("Value: %s\n", 42);
^
This message comes from the Sema (semantic analysis) phase, which checks that a literal format string matches the types of the variadic arguments passed to a printf-like function. If the types diverge, the compiler warns or errors, depending on the flags.
Cause & Diagnostic Table
| Cause | Typical Symptom | What to Inspect |
|---|---|---|
| Wrong argument type | Mismatch between conversion specifier and argument type | Check the type of each argument in the call list |
| Missing argument | Warning about more specifiers than arguments | Count specifiers vs. arguments |
| Extra argument | Warning about unused argument | Count specifiers vs. arguments |
| Incorrect width/precision specifier | Warning about invalid field width or precision | Inspect the specifier syntax |
| Non-literal format string | No warning, or -Wformat-nonliteral fires | Check whether the format is a literal |
| Custom printf-like function without format attribute | No checking occurs at the call site | Inspect the function declaration |
Ordered Verification Steps
- Locate the call site – Search for the function name (printf, fprintf, etc.) or a macro that expands to it.
- Inspect the format string literal – Is it a plain string or built from macros/concatenation? If it's not a literal, Sema cannot perform a full check.
- Match each conversion specifier – For every %conversion in the string, identify the corresponding argument in order.
- Validate argument types – Use Clang's type system:
intfor%d,char *for%s,doublefor%f, etc. Pay attention to signed/unsigned and size modifiers (e.g.,%lldvs%ld). - Check width/precision – If the specifier includes a width or precision (e.g.,
%10.2f), confirm that the argument can support it. - Count arguments – Ensure the number of arguments matches the number of conversion specifiers. Missing or surplus arguments trigger warnings.
- Verify custom function annotations – If you call a user-defined printf wrapper, confirm it's declared with
__attribute__((format(printf, 1, 2)))(or the equivalent for your language). Without this, Sema will not check it.
Fixes Tied to Findings
- Wrong type – Cast the argument to the expected type or change the specifier. Example:
// Wrong printf("%s", 42); // Better – use %d to match the actual type printf("%d", 42); - Missing argument – Add the missing value or remove the unused specifier.
- Extra argument – Remove the argument or the unused specifier.
- Incorrect width/precision – Adjust the specifier or use a different one that matches the argument's type.
- Non-literal format string – Refactor the code to use a literal or store the string in a constant variable. If dynamic formatting is required, consider disabling the warning with
-Wno-formatbut be aware of the risk. - Custom function missing format attribute – Add the attribute to the function declaration:
int my_printf(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
Escalation Criteria
- If warnings persist after correcting obvious mismatches, examine macro expansions. Use
clang -Eto view the pre-processed code and locate the real call. - Enable
-Werror=formatto treat warnings as errors. This forces the code to compile only when all format string checks pass. - For complex variadic wrappers that cannot be annotated, file a bug with the Clang project and provide the pre-processed source. The maintainers can add support or suggest a workaround.
- If the format string is built at runtime (e.g., from user input), consider sanitizing it before passing to printf or using a safer API such as
snprintf.
Verification Checklist
- Compile with
clang -Wall -Wformat -fsyntax-only test.cand confirm the warning appears. - Apply the suggested fix and recompile; the warning should disappear.
- Run
clang -ast-dump -fsyntax-only test.cto inspect the AST and confirm the call is being checked. - If the warning still shows, double-check that no macro or template is hiding the call.
Concrete Example
Suppose you have the following code:
#include <stdio.h>
void log_value(int level, const char *msg, ...) {
// ... implementation
}
int main() {
int val = 10;
log_value(1, "%d %s", val, 42); // Wrong: %s expects char *
return 0;
}
Clang will warn about the mismatch. To fix it:
// Add format attribute to the wrapper
int log_value(int level, const char *msg, ...) __attribute__((format(printf, 2, 3)));
// Correct the call
log_value(1, "%d %d", val, 42);
Re-compile and the warning disappears.
Limitations & Practical Checks
- Sema only checks literal format strings. Dynamic strings are not verified.
- Some system headers suppress format warnings; re-enable them with
-Wno-inhibit-format-warningif needed. - Older Clang versions may not support all format attribute syntax; ensure you're on a recent release.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.