为何C代码在TCC可正常运行,GCC下却出现运行异常?
问题:GCC编译后运行异常(Windows无输出、Ubuntu段错误),TCC正常
我是一名退休程序员,曾基于K&R标准编写C代码,之后从事了数十年C#和Transact-SQL(T-SQL)开发,现正重新学习C语言。以下代码段在Tiny C Compiler(TCC)中可正常编译运行,但使用GCC编译无报错后,在Windows 10环境运行无输出;在Ubuntu环境中,TCC仍正常工作,GCC编译运行则出现segmentation fault(core dump)。请问为何在GCC下会出现该问题?
#include <stdlib.h> #include <stdio.h> void UpCase(char * str); void LowCase(char * str); int main() { void (* func)(char *) = &UpCase; func("Ab12Cd3EfG*h&"); func = &LowCase; func("HiJ12kLm&No"); } void UpCase(char * str) { int i = 0; int ch = 0; for(i=0; str[i] != '\0'; i++) { ch = (int)str[i] + 0; if(ch > 96 && ch < 123) str[i] = (char)(ch - 32); } printf("UpCase: %s\n", str); } void LowCase(char * str) { int i = 0; int ch = 0; for(i=0; str[i] != '\0'; i++) { ch = (int)str[i] + 0; if(ch > 64 && ch < 91) str[i] = (char)(ch + 32); } printf("LowCase: %s\n", str); }
核心原因:修改字符串字面量触发未定义行为
C语言中,字符串字面量(如"Ab12Cd3EfG*h&")属于只读数据,现代编译器通常会将其放在程序的只读内存段(Linux的.rodata段、Windows的只读数据区)。你的代码中UpCase和LowCase函数直接修改了字符串字面量的内容,这违反了C标准的规定,属于未定义行为——不同编译器、操作系统的处理方式会完全不同:
- TCC:默认将字符串字面量分配在可写内存区域,所以修改操作不会触发错误,程序能正常运行;
- GCC:默认严格遵循标准,将字符串字面量放在只读内存中:
- Windows系统会静默拦截写入操作,导致程序逻辑异常(无输出);
- Ubuntu系统会直接抛出段错误(Segmentation Fault),因为写入只读内存是非法内存操作。
另外,K&R C时代对字符串字面量的约束相对宽松,但自C99标准开始,明确规定字符串字面量是const char[]类型,修改它们的行为是未定义的。
解决方案
要修复问题,必须避免直接修改字符串字面量,先将字符串内容复制到可写内存区域,比如栈数组或堆内存:
方案1:使用栈上的字符数组(适合短字符串)
#include <stdlib.h> #include <stdio.h> void UpCase(char * str); void LowCase(char * str); int main() { void (* func)(char *) = &UpCase; char str1[] = "Ab12Cd3EfG*h&"; // 把字面量复制到栈上的可写数组 func(str1); func = &LowCase; char str2[] = "HiJ12kLm&No"; func(str2); } // 以下UpCase、LowCase函数代码保持不变 void UpCase(char * str) { int i = 0; int ch = 0; for(i=0; str[i] != '\0'; i++) { ch = (int)str[i] + 0; if(ch > 96 && ch < 123) str[i] = (char)(ch - 32); } printf("UpCase: %s\n", str); } void LowCase(char * str) { int i = 0; int ch = 0; for(i=0; str[i] != '\0'; i++) { ch = (int)str[i] + 0; if(ch > 64 && ch < 91) str[i] = (char)(ch + 32); } printf("LowCase: %s\n", str); }
方案2:使用堆内存(适合长字符串)
#include <stdlib.h> #include <stdio.h> #include <string.h> void UpCase(char * str); void LowCase(char * str); int main() { void (* func)(char *) = &UpCase; // 用strdup把字面量复制到堆内存,返回可写指针 char *str1 = strdup("Ab12Cd3EfG*h&"); if (str1 == NULL) { perror("strdup failed"); return 1; } func(str1); free(str1); // 用完后必须释放堆内存 func = &LowCase; char *str2 = strdup("HiJ12kLm&No"); if (str2 == NULL) { perror("strdup failed"); return 1; } func(str2); free(str2); } // 以下UpCase、LowCase函数代码保持不变 void UpCase(char * str) { int i = 0; int ch = 0; for(i=0; str[i] != '\0'; i++) { ch = (int)str[i] + 0; if(ch > 96 && ch < 123) str[i] = (char)(ch - 32); } printf("UpCase: %s\n", str); } void LowCase(char * str) { int i = 0; int ch = 0; for(i=0; str[i] != '\0'; i++) { ch = (int)str[i] + 0; if(ch > 64 && ch < 91) str[i] = (char)(ch + 32); } printf("LowCase: %s\n", str); }
额外建议
为了从源头避免这类问题,建议根据函数是否修改参数,明确标注指针的const属性:
- 如果函数不需要修改字符串,参数声明为
const char* str; - 如果函数需要修改字符串,保持
char* str,但调用时必须确保传入的是可写内存(比如栈数组、堆内存)。
内容的提问来源于stack exchange,提问作者Chief Cook
相关产品推荐
相关产品推荐

