PTA——段错误

在PTA上做题,做出来个段错误,一查吓一跳,发篇博客冷静下。(#^.^#)

1076 Wifi密码 (15 分)

下面是微博上流传的一张照片:“各位亲爱的同学们,鉴于大家有时需要使用 wifi,又怕耽误亲们的学习,现将 wifi 密码设置为下列数学题答案:A-1;B-2;C-3;D-4;请同学们自己作答,每两日一换。谢谢合作!!~”—— 老师们为了促进学生学习也是拼了…… 本题就要求你写程序把一系列题目的答案按照卷子上给出的对应关系翻译成 wifi 的密码。这里简单假设每道选择题都有 4 个选项,有且只有 1 个正确答案。

//我的代码
#include 
#define MAX 101
#define SIZE 5
int main()
{
	int N = 0;
	char str[MAX][SIZE];
	scanf("%d", &N);
	if (N > 100 || N < 0)
		return 0;
	for (int i = 0; i < 4 * N; i++)
	{
		scanf("%s", str[i]);
		if (str[i][2] == 'T')
		{
			if (str[i][0] == 'A')
				putchar('1');
			else if (str[i][0] == 'B')
				putchar('2');
			else if (str[i][0] == 'C')
				putchar('3');
			else if (str[i][0] == 'D')
				putchar('4');
		}
	}
	return 0;
}

PTA——段错误_第1张图片

  • 修改后的正确代码:将MAX扩大为原来的5倍,段错误是由于N最大可取到100,而MAX没有满足MAX=4*N的条件所导致 
#include 
#define MAX 501
#define SIZE 5
int main()
{
	int N = 0;
	char str[MAX][SIZE];
	scanf("%d", &N);
	if (N > 100 || N < 0)
		return 0;
	for (int i = 0; i < 4 * N; i++)
	{
		scanf("%s", str[i]);
		if (str[i][2] == 'T')
		{
			if (str[i][0] == 'A')
				putchar('1');
			else if (str[i][0] == 'B')
				putchar('2');
			else if (str[i][0] == 'C')
				putchar('3');
			else if (str[i][0] == 'D')
				putchar('4');
		}
	}
	return 0;
}

PTA——段错误_第2张图片

 

一、什么是段错误?

一旦一个程序发生了越界访问,cpu 就会产生相应的保护,于是 segmentation fault 就出现了,通过上面的解释,段错误应该就是访问了不可访问的内存,这个内存区要么是不存在的,要么是受到系统保护的,还有可能是缺少文件或者文件损坏。

二、段错误产生的原因

  • 下面是一些典型的段错误的原因:
  1. 非关联化空指针——这是特殊情况由内存管理硬件
  2. 试图访问一个不存在的内存地址(在进程的地址空间)
  3. 试图访问内存的程序没有权利(如内核结构流程上下文)
  4. 试图写入只读存储器(如代码段)

1、访问不存在的内存地址

在C代码,分割错误通常发生由于指针的错误使用,特别是在C动态内存分配。非关联化一个空指针总是导致段错误,但野指针和悬空指针指向的内存,可能会或可能不会存在,而且可能或不可能是可读的还是可写的,因此会导致瞬态错误。

#include   
  
int main (void)  
{  
    int *ptr = NULL;  
    *ptr = 0;  
    return 0;  
} 

现在,非关联化这些变量可能导致段错误:非关联化空指针通常会导致段错误,阅读时从野指针可能导致随机数据但没有段错误,和阅读从悬空指针可能导致有效数据,然后随机数据覆盖。

2、访问系统保护的内存地址 

#include   
  
int main (void)  
{  
    int *ptr = (int *)0;  
    *ptr = 100;  
    return 0;  
} 

3、访问只读的内存地址

写入只读存储器提出了一个 segmentation fault,这个发生在程序写入自己的一部分代码段或者是只读的数据段,这些都是由操作系统加载到只读存储器。

#include   
#include   
  
int main (void)  
{  
    char *ptr = "test";  
    strcpy (ptr, "TEST");  
    return 0;  
}  
#include   
  
int main (void)  
{  
    char *ptr = "hello";  
    *ptr = 'H';  
    return 0;  
}  
输出结果:  
段错误(核心已转储)  

上述例子ANSI C代码通常会导致段错误和内存保护平台。它试图修改一个字符串文字,这是根据ANSI C标准未定义的行为。大多数编译器在编译时不会抓,而是编译这个可执行代码,将崩溃。

包含这个代码被编译程序时,字符串“hello”位于rodata部分程序的可执行文件的只读部分数据段。当加载时,操作系统与其他字符串和地方常数只读段的内存中的数据。当执行时,一个变量 ptr 设置为指向字符串的位置,并试图编写一个H字符通过变量进入内存,导致段错误。编译程序的编译器不检查作业的只读的位置在编译时,和运行类unix操作系统产生以下运行时发生 segmentation fault。

可以纠正这个代码使用一个数组而不是一个字符指针,这个栈上分配内存并初始化字符串的值:

#include   
  
int main (void)  
{  
    char ptr[] = "hello";  
    ptr[0] = 'H';  
    return 0;  
}  

即使不能修改字符串(相反,这在C标准未定义行为),在C char *类型,所以没有隐式转换原始代码,在c++的 const char *类型,因此有一个隐式转换,所以编译器通常会抓住这个特定的错误。

4、空指针废弃

因为是一个很常见的程序错误空指针废弃(读或写在一个空指针,用于C的意思是“没有对象指针”作为一个错误指示器),大多数操作系统内存访问空指针的地址,这样它会导致段错误。

#include   
  
int main (void)  
{  
    int *ptr = NULL;  
    printf ("%d\n", *ptr);  
    return 0;  
} 

这个示例代码创建了一个空指针,然后试图访问它的值(读值)。在运行时在许多操作系统中,这样做会导致段错误。

非关联化一个空指针,然后分配(写一个值到一个不存在的目标)也通常会导致段错误。

#include   
  
int main (void)  
{  
    int *ptr = NULL;  
    *ptr = 1;  
    return 0;  
}  

下面的代码包含一个空指针,但当编译通常不会导致段错误,值是未使用的。因此,废弃通常会被优化掉,死代码消除。

#include   
  
int main (void)  
{  
    int *ptr = NULL;  
    *ptr;  
    return 0;  
}  

还有,比如malloc 动态分配内存,释放、置空完成后,不可再使用该指针。

#include   
#include   
#include   
  
int main()  
{  
    char* str=(char* )malloc(100);  
    if(*str)  
    {  
        return;   
    }  
    strcpy(str,"hello");  
    printf("%s\n",str);  
    free(str);  
    str=NULL;  
    strcpy(str,"abcdef");  
    return 0;  
}  
输出结果:  
hello  
段错误 (核心已转储)

5、堆栈溢出

#include   
#include   
  
int main (void)  
{  
    main ();  
    return 0;  
}  
输出结果:  
段错误(核心已转储)  

上述例子的无限递归,导致的堆栈溢出会导致段错误,但无线递归未必导致堆栈溢出,优化执行的编译器和代码的确切结构。在这种情况下,遥不可及的代码(返回语句)行为是未定义的。因此,编译器可以消除它,使用尾部调用优化,可能导致没有堆栈使用。其他优化可能包括将递归转换成迭代,给出例子的结构功能永远会导致程序运行,虽然可能不是其他堆栈溢出。

6、内存越界(数组越界,变量类型不一致等)

#include   
  
int main (void)  
{  
    char test[10];  
    printf ("%c\n", test[100000]);  
    return 0;  
}  
输出结果:  
段错误(核心已转储)  

三、一些注意事项

  1. 出现段错误时,首先应该想到段错误的定义,从它出发考虑引发错误的原因。
  2. 在使用指针时,定义了指针后记得初始化指针,在使用的时候记得判断是否为 NULL
  3. 在使用数组时,注意数组是否被初始化,数组下标是否越界,数组元素是否存在等
  4. 在访问变量,注意变量所占地址空间是否已经被程序释放掉
  5. 在处理变量时,注意变量的格式控制是否合理等

参看:C语言再学习 -- GCC编译过程
1.段错误的定义
Ansers.com
http://www.answers.com
Definition of "Segmentation fault"
http://www.faqs.org/qa/qa-673.html
2.《什么是段错误》
http://www.linux999.org/html_sql/3/132559.htm
3.《Segment fault 之永远的痛》
http://www.linuxforum.net/forum/gshowflat.php?Cat=&Board=program&Number=193239&page=2&view=collapsed&sb=5&o=all&fpart=
4.《段错误bug的调试》
http://www.cublog.cn/u/5251/showart.php?id=173718

你可能感兴趣的:(C/C++)