Why is the riscv32 executable compiled by clang several times larger than that compiled by gcc?

I am compiling a riscv32 architecture baremetal helloworld project, compiled with riscv32-unknown -elf-gcc, and the size of the generated executable file is:

riscv32-unknown-elf-size build/helloworld.elf
    text     data      bss       dec     hex   filename
11348      112    4336   15796   3db4   build/helloworld.elf

However, I compiled with clang, and the result is:

llvm-size build/helloworld.elf
    text     data      bss       dec     hex   filename
55500    2488    4236   62224   f310   build/helloworld.elf

Did you use any compilation flag? If yes, which?

You may want to add -### to your compile commands also, to show you what the final command looks like. Since some options may be set to different defaults between the two compilers.

this is the gcc Makefile:

######################################
# target
######################################
TARGET = helloworld


######################################
# building variables
######################################
# optimization
OPT = -O2


#######################################
# paths
#######################################
# Build path
BUILD_DIR = build

######################################
# source
######################################
# C sources
C_SOURCES += $(wildcard SoC/gd32vf103/Board/gd32vf103v_rvstar/Source/*.c)
C_SOURCES += $(wildcard SoC/gd32vf103/Common/Source/Drivers/*.c)
C_SOURCES += $(wildcard SoC/gd32vf103/Common/Source/Stubs/newlib/*.c)
C_SOURCES += $(wildcard SoC/gd32vf103/Common/Source/*.c)
C_SOURCES += $(wildcard application/baremetal/helloworld/*.c)


# ASM sources
ASM_SOURCES += $(wildcard SoC/gd32vf103/Common/Source/GCC/*.S)	

#######################################
# binaries
#######################################

# The gcc compiler bin path can be either defined in make command via GCC_PATH variable (> make GCC_PATH=xxx)
# either it can be added to the PATH environment variable.
CC = riscv32-unknown-elf-gcc
CXX = riscv32-unknown-elf-g++
AS = riscv32-unknown-elf-gcc -x assembler-with-cpp
CP = riscv32-unknown-elf-objcopy
SZ = riscv32-unknown-elf-size
HEX = $(CP) -O ihex
BIN = $(CP) -O binary -S
 
#######################################
# CFLAGS
#######################################

# mcu
MCU = -march=rv32imac -mabi=ilp32 -mcmodel=medlow

# macros for gcc
# AS defines
AS_DEFS = \
-DSYSTEM_CLOCK=108000000 \
-DSYSCLK_USING_HXTAL \
-DDOWNLOAD_MODE=DOWNLOAD_MODE_FLASHXIP \
-DDOWNLOAD_MODE_STRING=\"FLASHXIP\"

# C defines
C_DEFS =  \
-DSYSTEM_CLOCK=108000000 \
-DSYSCLK_USING_HXTAL \
-DDOWNLOAD_MODE=DOWNLOAD_MODE_FLASHXIP \
-DDOWNLOAD_MODE_STRING=\"FLASHXIP\"

# common includes
COM_INCLUDES = \
-I/opt/riscv/riscv32-unknown-elf/include \
-I/opt/riscv/riscv32-unknown-elf/include/c++/12.2.0 \
-I/opt/riscv/riscv32-unknown-elf/include/c++/12.2.0/riscv32-unknown-elf \
-I/opt/riscv/riscv32-unknown-elf/include/c++/12.2.0/backward \
-I/opt/riscv/lib/gcc/riscv32-unknown-elf/12.2.0/include \
-I/opt/riscv/lib/gcc/riscv32-unknown-elf/12.2.0/include-fixed

# AS includes
AS_INCLUDES = \
$(COM_INCLUDES) \
-I application/baremetal/helloworld/ \
-I NMSIS/Core/Include \
-I SoC/gd32vf103/Board/gd32vf103v_rvstar/Include \
-I SoC/gd32vf103/Common/Include \
-I application/baremetal/helloworld/inc



# C includes
C_INCLUDES =  \
$(COM_INCLUDES) \
-I application/baremetal/helloworld/ \
-I NMSIS/Core/Include \
-I SoC/gd32vf103/Board/gd32vf103v_rvstar/Include \
-I SoC/gd32vf103/Common/Include \
-I application/baremetal/helloworld/inc


# compile gcc flags
ASFLAGS = $(MCU) $(OPT)  $(AS_DEFS) $(AS_INCLUDES) -g -ffunction-sections -fdata-sections -fno-common --specs=nano.specs --specs=nosys.specs

# Generate dependency information
ASFLAGS += -MMD -MT $@ -MF "$(@:%.o=%.o.d)"


CFLAGS += $(MCU) $(OPT) $(C_DEFS) $(C_INCLUDES)  -g -ffunction-sections -fdata-sections -fno-common --specs=nano.specs --specs=nosys.specs

# Generate dependency information
CFLAGS += -MMD -MT $@ -MF "$(@:%.o=%.o.d)"


#######################################
# LDFLAGS
#######################################
# link script
LDSCRIPT = SoC/gd32vf103/Board/gd32vf103v_rvstar/Source/GCC/gcc_gd32vf103_flashxip.ld

# libraries
LD_INCLUDES = \
-I application/baremetal/helloworld/ \
-I NMSIS/Core/Include \
-I SoC/gd32vf103/Board/gd32vf103v_rvstar/Include \
-I SoC/gd32vf103/Common/Include \
-I application/baremetal/helloworld/inc
LDFLAGS += $(MCU) $(COM_FALGS) $(C_DEFS) $(OPT) -g -ffunction-sections -fdata-sections -fno-common --specs=nano.specs --specs=nosys.specs -T $(LDSCRIPT)  $(LD_INCLUDES)
LDFLAGS += -nostartfiles
LDFLAGS += -Wl,-Map=helloworld.map
LDFLAGS += -Wl,--gc-sections
LDFLAGS += -Wl,--check-sections
LDFLAGS += -Wl,--start-group -lstdc++ -Wl,--end-group -u _isatty -u _write -u _sbrk -u _read -u _close -u _fstat -u _lseek

all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin
#######################################
# build the application
#######################################
# list of objects
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
vpath %.c $(sort $(dir $(C_SOURCES)))

# list of ASM program objects
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.S=.o)))
vpath %.S $(sort $(dir $(ASM_SOURCES)))

$(BUILD_DIR)/%.o: %.S Makefile | $(BUILD_DIR)
	$(AS) -c $(ASFLAGS) $< -o $@

$(BUILD_DIR)/%.o: %.s Makefile | $(BUILD_DIR)
	$(AS) -c $(ASFLAGS) $< -o $@

$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR) 
	$(CC) -c $(CFLAGS)  $< -o $@

$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
	$(CC) $(OBJECTS) $(LDFLAGS) -o $@
	$(SZ) $@

$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
	$(HEX) $< $@
	
$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
	$(BIN) $< $@

$(BUILD_DIR):
	mkdir $@

#######################################
# clean up
#######################################
clean:
	-rm -fR $(BUILD_DIR)
  
#######################################
# dependencies
#######################################
-include $(wildcard $(BUILD_DIR)/*.d)

# *** EOF ***

and this is my clang Makefile:

######################################
# target
######################################
TARGET = helloworld


######################################
# building variables
######################################
# optimization
OPT = -O2


#######################################
# paths
#######################################
# Build path
BUILD_DIR = build

######################################
# source
######################################
# C sources
C_SOURCES += $(wildcard SoC/gd32vf103/Board/gd32vf103v_rvstar/Source/*.c)
C_SOURCES += $(wildcard SoC/gd32vf103/Common/Source/Drivers/*.c)
C_SOURCES += $(wildcard SoC/gd32vf103/Common/Source/Stubs/newlib/*.c)
C_SOURCES += $(wildcard SoC/gd32vf103/Common/Source/*.c)
C_SOURCES += $(wildcard application/baremetal/helloworld/*.c)


# ASM sources
ASM_SOURCES += $(wildcard SoC/gd32vf103/Common/Source/GCC/*.S)	

#######################################
# binaries
#######################################

# The gcc compiler bin path can be either defined in make command via GCC_PATH variable (> make GCC_PATH=xxx)
# either it can be added to the PATH environment variable.
CC = clang
CXX = clang++
AS = clang -x assembler-with-cpp
CP = llvm-objcopy
SZ = llvm-size
HEX = $(CP) -O ihex
BIN = $(CP) -O binary -S
 
#######################################
# CFLAGS
#######################################

# mcu
MCU = --target=riscv32-unknown-elf -march=rv32imac -mabi=ilp32 -mcmodel=medlow

# macros for gcc
# AS defines
AS_DEFS = \
-DSYSTEM_CLOCK=108000000 \
-DSYSCLK_USING_HXTAL \
-DDOWNLOAD_MODE=DOWNLOAD_MODE_FLASHXIP \
-DDOWNLOAD_MODE_STRING=\"FLASHXIP\"

# C defines
C_DEFS =  \
-DSYSTEM_CLOCK=108000000 \
-DSYSCLK_USING_HXTAL \
-DDOWNLOAD_MODE=DOWNLOAD_MODE_FLASHXIP \
-DDOWNLOAD_MODE_STRING=\"FLASHXIP\"

# common includes
COM_INCLUDES = \
-I/opt/riscv/riscv32-unknown-elf/include \
-I/opt/riscv/riscv32-unknown-elf/include/c++/12.2.0 \
-I/opt/riscv/riscv32-unknown-elf/include/c++/12.2.0/riscv32-unknown-elf \
-I/opt/riscv/riscv32-unknown-elf/include/c++/12.2.0/backward \
-I/opt/riscv/lib/gcc/riscv32-unknown-elf/12.2.0/include \
-I/opt/riscv/lib/gcc/riscv32-unknown-elf/12.2.0/include-fixed

# AS includes
AS_INCLUDES = \
-I application/baremetal/helloworld/ \
-I NMSIS/Core/Include \
-I SoC/gd32vf103/Board/gd32vf103v_rvstar/Include \
-I SoC/gd32vf103/Common/Include \
-I application/baremetal/helloworld/inc



# C includes
C_INCLUDES =  \
-I application/baremetal/helloworld/ \
-I NMSIS/Core/Include \
-I SoC/gd32vf103/Board/gd32vf103v_rvstar/Include \
-I SoC/gd32vf103/Common/Include \
-I application/baremetal/helloworld/inc

# common flags
COM_FALGS = --sysroot=/opt/riscv/riscv32-unknown-elf --gcc-toolchain=/opt/riscv/

# compile gcc flags
ASFLAGS = $(MCU) $(COM_FALGS) $(OPT)  $(AS_DEFS) $(AS_INCLUDES) -g -ffunction-sections -fdata-sections -fno-common --specs=nano.specs --specs=nosys.specs

# Generate dependency information
ASFLAGS += -MMD -MT $@ -MF "$(@:%.o=%.o.d)"


CFLAGS += $(MCU) $(COM_FALGS) $(OPT) $(C_DEFS) $(C_INCLUDES)  -g -ffunction-sections -fdata-sections -fno-common --specs=nano.specs --specs=nosys.specs 

# Generate dependency information
CFLAGS += -MMD -MT $@ -MF "$(@:%.o=%.o.d)"


#######################################
# LDFLAGS
#######################################
# link script
LDSCRIPT = SoC/gd32vf103/Board/gd32vf103v_rvstar/Source/GCC/gcc_gd32vf103_flashxip.ld

# libraries
LD_INCLUDES = \
-I application/baremetal/helloworld/ \
-I NMSIS/Core/Include \
-I SoC/gd32vf103/Board/gd32vf103v_rvstar/Include \
-I SoC/gd32vf103/Common/Include \
-I application/baremetal/helloworld/inc
LDFLAGS += $(MCU) $(COM_FALGS) $(C_DEFS) $(OPT) -g -ffunction-sections -fdata-sections -fno-common --specs=nano.specs --specs=nosys.specs -T $(LDSCRIPT)  $(LD_INCLUDES)
LDFLAGS += -nostartfiles
LDFLAGS += -Wl,-Map=helloworld.map
LDFLAGS += -Wl,--gc-sections
LDFLAGS += -Wl,--check-sections
LDFLAGS += -Wl,--start-group -lstdc++ -Wl,--end-group -u _isatty -u _write -u _sbrk -u _read -u _close -u _fstat -u _lseek

all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin
#######################################
# build the application
#######################################
# list of objects
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
vpath %.c $(sort $(dir $(C_SOURCES)))

# list of ASM program objects
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.S=.o)))
vpath %.S $(sort $(dir $(ASM_SOURCES)))

$(BUILD_DIR)/%.o: %.S Makefile | $(BUILD_DIR)
	$(AS) -c $(ASFLAGS) $< -o $@

$(BUILD_DIR)/%.o: %.s Makefile | $(BUILD_DIR)
	$(AS) -c $(ASFLAGS) $< -o $@

$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR) 
	$(CC) -c $(CFLAGS)  $< -o $@

$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
	$(CC) $(OBJECTS) $(LDFLAGS) -o $@
	$(SZ) $@

$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
	$(HEX) $< $@
	
$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
	$(BIN) $< $@

$(BUILD_DIR):
	mkdir $@

#######################################
# clean up
#######################################
clean:
	-rm -fR $(BUILD_DIR)
  
#######################################
# dependencies
#######################################
-include $(wildcard $(BUILD_DIR)/*.d)

# *** EOF ***

As a novice, I don’t understand the specific meaning of these compilation flags. I hope the two Makefiles I uploaded can provide some clues.

Do both programs work? You can compare the helloworld.map file for clang and gcc to see the differences in what object files were included in the ELF file. Maybe clang is including stuff that’s not needed. Using -### will help you see if switches like -ffunction-sections and -fdata-sections are actually being seen by clang.

The difference between them is that GCC uses “–specs=nano. specs --specs=nosys. specs”. When GCC deletes these two flags, the difference between the size of the compiled executable file is very small.Does Clang have such a similar flag, which can greatly reduce the size of the executable file?

All --specs= does is point GCC at a file with more command-line options to use with some slightly fancy syntax that makes it able to choose flags dynamically. Clang’s closest equivalent is config files. But the important thing is to look at the flags in those two files and see what the important flags are, but I’m going to guess it’s because it makes it pick a different set of libraries to link against that are much smaller in size, i.e. it has nothing to do with code generation.

I can see that these two flags really specify two libraries for GCC. For example, “– specs=nano. specs” specifies the use of “libc_nano. a” for GCC instead of “libc. a”. “libc_nano. a” is a small “libc. a” specially designed for GCC, which reduces the volume of executable files generated. How should Clang specify to use “libc_nano. a” instead of “libc. a”?

-nostdlib -lc_nano?

Thank you for your help.This problem is resolved now. I added these two flags in the link phase. At first, it reported an error. I carefully checked the GCC map file and changed “-lc_nano” to “-lgcc, -lg_nano”. Now, the file size compiled by Clang is approximately the same as that compiled by GCC using “–specs=nano. specs --specs=nosys. specs” flags.