正确使用goto语句

问题起源:
60年代中期以后,计算机硬件技术日益进步,计算的存贮容量、运算速度和可靠性明显提高,生产硬件的成本不断降低。计算机价格的下跌为它的广泛应用创造了极好的条件。在这种形势下,迫切要求计算机软件也能与之相适应。因而,一些开发大型软件系统的要求提了出来。然而软件技术的进步一直未能满足形势发展的需要,在大型软件的开发过程中出现了复杂程度高、研制周期长、正确性难以保证的三大难题。遇到的问题找不到解决办法,致使问题堆积起来,形成了人们难以控制的局面,出现了所谓的“ 软件危机”。为了克服这一危机,一方面需要对程序设计方法、程序的正确性和软件的可靠性等问题进行系列的研究;另一方面,也需要对软件的编制、测试、维护和管理的方法进行研究,从而产生了 程序设计方法学
goto语句是有害的观点:
1968年,Edsger Wybe Dijkstra 首先提出“GOTO语句是有害的”论点,向传统程序设计方法提出了挑战,从而引起了人们对程序设计方法讨论的普遍重视。
goto语句的争论:
在60年代末和70年代初,关于GOTO语句的用法的争论比较激烈。主张从高级程序语言中去掉GOTO语句的人认为,GOTO语句是对程序结构影响最大的一种有害的语句,他们的主要理由是:GOTO语句使程序的静态结构和动态结构不一致,从而使程序难以理解,难以查错。去掉GOTO语句后,可直接从程序结构上反映程序运行的过程。这样,不仅使程序结构清晰,便于理解,便于查错,而且也有利于程序的正确性证明。
持反对意见的人认为,GOTO语句使用起来比较灵活,而且有些情形能提高程序的效率。若完全删去GOTO语句,有些情形反而会使程序过于复杂,增加一些不必要的计算量。
关于goto语句的解决方法:
1974年,D·E·克努斯对于GOTO语句争论作了全面公正的评述,其基本观点是:不加限制地使用GOTO语句,特别是使用往回跳的GOTO语句,会使程序结构难于理解,在这种情形,应尽量避免使用GOTO语句。但在另外一些情况下,为了提高程序的效率,同时又不至于破坏程序的良好结构,有控制地使用一些GOTO语句也是必要的。用他的话来说就是:“在有些情形,我主张删掉GOTO语句;在另外一些情形,则主张引进GOTO语句。”从此,使这场长达10年之久的争论得以平息。
后来,G·加科皮尼和C·波姆从理论上证明了:任何程序都可以用顺序、分支和重复结构表示出来。这个结论表明,从高级程序语言中去掉GOTO语句并不影响高级程序语言的编程能力,而且编写的程序的结构更加清晰。
goto语句的结果:
在C/C++等高级编程语言中保留了goto语句,但被建议不用或少用。在一些更新的高级编程语言,如Java不提供goto语句,它虽然指定goto作为关键字,但不支持它的使 用,使程序简洁易读;尽管如此后来的c#还是支持goto语句的,goto语句一个好处就是可以保证程序存在唯一的出口,避免了过于庞大的if嵌套。
二.可以考虑使用goto的情形
1.从多重循环中直接跳出
很多人建议废除C++/C的goto语句,以绝后患。但实事求是地说,错误是程序员自己造成的,不是goto的过错。goto 语句至少有一处可显神通,它能从多重循环体中一下子跳到外面,用不着写很多次的break语句。例如:
for(......)
{
for(....)
{
for(.....)
{
// 如何冲出重重包围?
}
}
}
break;只能跳出单层的循环,return将整个函数都返回了,没法再继续了,显然也不行,所以我们想到了goto。如果是在陷入了很深层次的循环里想要跳出最外层的循环,用 goto 直接跳出却比用 break 一个循环一个循环地跳出要好得多。有人甚至形象比喻说:“就像楼房着火了,来不及从楼梯一级一级往下走,可从窗口跳出火坑。” 其实,你可以将 break 和 continue 理解成弱化了的 goto 语句。
2. 出错时清除资源
如果一个函数有多个出口,则在每个出口处,会产生巨大的退出代码,如下例一,每个函数只能有一个出口,所有的资源释放必须放在出口统一解决,那全部使用大括号,十几个,几十个if判断条件下来,你数数你的大括号有多深?这种代码可读性不好,一旦写错了,难于寻找错误。所有这些问题,一个goto就解决了。
当程序要分配和清除资源时(像内存、或处理字形、窗口、打印机),这种情形下用goto通常是为了复制代码或清除资源。若遇到这种情况,程序员就要掂量是 goto 的缺点令人讨厌呢?还是复制代码那令人头痛的维护更讨厌呢?最后还是认为 goto 的缺点更可忍受。
例子一:不用goto,想想需要申请的 指针是10个的话,程序怎么写?
void Func(void)
{
char* p1=null;
char* p2=null;
char* p3=null;
p1=(char*)malloc(10);
if(!p1) return;
p2=(char*)malloc(10);
if(!p2)
{
free(p1);
p1=null;
return;
}
p3=(char*)malloc(10);
if(!p3)
{
free(p1);
p1=null;
free(p2);
p2=null;
return;
}
……
……
…… //指针使用过程
if(p1)
{
free(p1);
p1=null;
}
if(p2)
{
free(p2);
p2=null;
}
if(p3)
{
free(p3);
p3=null;
}
}
例子二:用goto
void Func(void)
{
char* p1=null;
char* p2=null;
char* p3=null;
p1=(char*)malloc(10);
if(!p1) goto Func_End_Process;
p2=(char*)malloc(10);
if(!p2) goto Func_End_Process;
p3=(char*)malloc(10);
if(!p3) goto Func_End_Process;
……
……
…… //指针使用过程
Func_End_Process:
if(p1)
{
free(p1);
p1=null;
}
if(p2)
{
free(p2);
p2=null;
}
if(p3)
{
free(p3);
p3=null;
}
}
3.可增加程序的清晰度的情况。
若不使用goto语句会使功能模糊,有时候使用goto语句,一眼就看清楚了程序的意图,可用那些对应的循环break语句等实现的语句段,要想老半天才搞清楚程序意图的情况,也可考虑使用goto语句。
三.不加限制地使用goto带来的弊端
1).很明显,不加限制地使用goto破坏了清晰的程序结构,使程序的可读性变差,甚至成为不可维护的"面条代码"。例如下例:
[code=C/C++]
A: //code section A
//code
goto B;
//code
goto C;
B: //code section B
//code
goto A;
//code
goto C;
C: //code section C
//code
//goto B;
//code
goto A;
[/code]
这样好像已经能够说明问题了,随着标签的增多,带来的混乱局面是很难扭转的,对调试,走读,理解代码都会造成很大的障碍,如果你写这样的代码,那代码维护绝对会是一场 噩梦。
2). 不加限制地使用goto经常带来错误或隐患。它可能跳过了某些对象的构造、变量的初始化、重要的计算等语句,例如:
goto state;
String s1, s2; // 被goto 跳过
int sum = 0; // 被goto 跳过
…..
……
state:
……
如果 编译器不能发觉此类错误,每用一次goto 语句都可能留下隐患。
四.Goto语句与 结构化程序设计
goto语句问题的提出直接推动了结构化程序设计(structured programming)的思想和 程序设计方法学的诞生和发展。 结构化程序设计方法引入了工程思想和结构化思想,使大型软件的开发和编程都得到了极大的改善。
结构化程序设计方法的主要原则可以概括为自顶向下, 逐步求精,模块化,限制使用goto语句。
1.自顶向下:程序设计时,应先考虑总体,后考虑细节;先考虑全局目标,后考虑局部目标。不要一开始就过多追求众多的细节,先从最上层总目标开始设计,逐步使问题具体化。
2.逐步求精:对复杂问题,应设计一些子目标作为过渡,逐步细化。
3.模块化:一个复杂问题,肯定是由若干稍简单的问题构成。模块化是把程序要解决的总目标分解为子目标,再进一步分解为具体的小目标,把每一个小目标称为一个模块。
4.限制使用goto语句
结构化程序设计方法的起源来自对goto语句的认识和争论。肯定的结论是,在块和进程的非正常出口处往往需要用goto语句,使用goto语句会使程序执行效率较高;在合成程序目标时,goto语句往往是有用的,如返回语句用goto。否定的结论是,goto语句是有害的,是造成程序混乱的祸根,程序的质量与goto语句的数量呈反比,应该在所有高级 程序设计语言中取消goto语句。取消goto语句后,程序易于理解、易于排错、容易维护,容易进行正确性证明。作为争论的结论,1974年Knuth发表了令人信服的总结,并证实了:
(1)goto语句确实有害,应当尽量避免;
(2)完全避免使用goto语句也并非是个明智的方法,有些地方使用goto语句,会使程序流程更清楚、效率更高。
(3)争论的焦点不应该放在是否取消goto语句上,而应该放在用什么样的程序结构上。其中最关键的是,应在以提高程序清晰性为目标的 结构化方法中限制使用goto语句
五.关于goto使用语句的一些建议
goto语句在结构化编程技术出来后,被当作破坏 结构化程序的典型代表,可以说,在 结构化程序设计年代,goto语句就像洪水猛兽一样,程序员都唯恐避之不及;可后来在微软的一些例子程序中经常把goto语句用来处理出错,当出错时,goto到函数要退出的一个label那里进行资源释放等操作。那么,goto语句是不是只可以用于出错处理,其他地方都不可以用了呢?下列关于使用goto语句的原则可以供读者参考。
1) 使用goto语句只能goto到同一函数内,而不能从一个函数里goto到另外一个函数里。
2) 使用goto语句在同一函数内进行goto时,goto的起点应是函数内一段小功能的结束处,goto的目的label处应是函数内另外一段小功能的开始处。
3) 不能从一段复杂的执行状态中的位置goto到另外一个位置,比如,从多重嵌套的循环判断中跳出去就是不允许的。
4)应该避免向两个方向跳转。这样最容易导致"面条代码"。
<think>嗯,用户现在想知道在C/C++线程中使用goto语句正确方法以及可行性,特别是在pthread_create创建的线程中如何正确使用goto,需要注意哪些问题。我需要结合之前的讨论来回答这个问题。 首先,回顾一下之前的问题,用户之前询问了如何通过pthread_create传递结构体参数,我详细解释了动态内存分配和线程安全的问题。现在的问题转向了线程函数中使用goto语句的可行性,这似乎是一个新的但相关的话题。 根据用户提供的引用,引用[1]中的测试程序展示了多个线程访问共享变量tickets时的竞态条件问题,引用[2]提到了使用清理处理函数来解锁互斥锁,引用[3]则讨论了volatile在多线程环境中的应用。这些引用可能对当前的问题有帮助,尤其是关于线程安全和资源管理的内容。 接下来,我需要考虑goto在线程函数中的使用。首先,goto在C语言中是合法的,但通常不建议使用,因为它可能导致代码结构混乱,难以维护。但在某些特定情况下,比如错误处理或资源释放,goto可以简化代码结构。 用户的问题是,如何在pthread创建的线程中正确使用goto。需要注意的问题可能包括:跳转时的资源管理、避免死锁、保证局部变量的有效性,以及线程安全等。例如,在跳转前是否需要释放互斥锁或关闭文件,否则可能导致资源泄漏或死锁。 结合引用[2]中的清理处理函数,如果在使用goto跳转前,已经获取了互斥锁,那么需要确保在跳转前释放锁,或者使用清理函数来处理。否则,可能会导致其他线程无法获取锁,从而引发死锁。 另外,引用[1]中的例子展示了多线程共享变量的问题,如果在线程函数中使用goto,并且涉及共享资源的访问,必须确保临界区的正确保护,比如使用互斥锁,避免竞态条件。 同时,引用[3]提到的volatile变量可能在线程中使用,但需要注意到goto并不会直接影响volatile变量的行为,但如果在循环中使用goto跳出,而循环依赖于volatile变量,可能需要确保条件判断的正确性。 总结用户的问题,他们可能希望了解在线程函数中使用goto是否安全,如何正确使用,以及需要注意的陷阱。需要强调goto的合理使用场景,比如错误处理的集中退出,同时避免在复杂的流程控制中使用,导致代码难以维护。 另外,用户可能担心goto会影响线程的局部变量或栈状态。需要解释,只要goto在同一个函数内跳转,且不跨越变量初始化,是安全的。但需要注意,跳转标签后的代码可能绕过某些变量的初始化,导致未定义行为。 最后,需要提供示例代码,展示正确使用goto的情况,比如在错误处理时跳转到资源释放的标签,同时使用互斥锁和清理函数,确保资源安全。同时,指出应避免的情况,如循环内随意跳转导致逻辑混乱。</think>### 在`pthread_create`线程中使用`goto`语句的可行性及注意事项 在C/C++中,`goto`语句本身是语言特性,其使用与线程无关,但在多线程环境下使用`goto`需要特别注意**资源管理**和**代码可维护性**。以下是具体分析及实现建议: --- #### **1. `goto`的适用场景** `goto`在以下场景中可能合理: - **错误处理集中化**:跳转到统一的错误处理标签(如释放资源、解锁互斥量)。 - **跳出多层嵌套循环**:避免复杂的循环控制逻辑。 ```c void* thread_func(void* arg) { Resource* res = allocate_resource(); if (!res) goto error; if (pthread_mutex_lock(&mutex) != 0) goto release_res; // 临界区操作 if (operation_failed) goto unlock_mutex; unlock_mutex: pthread_mutex_unlock(&mutex); release_res: free_resource(res); error: return NULL; } ``` --- #### **2. 关键注意事项** 1. **资源泄漏风险** 若`goto`跳过了资源释放操作(如`free`、`close`、`pthread_mutex_unlock`),会导致内存泄漏或死锁。 **解决方案**: - 使用**清理处理函数**(`pthread_cleanup_push`/`pthread_cleanup_pop`)确保资源释放[^2]。 - 示例: ```c void cleanup(void* arg) { pthread_mutex_unlock((pthread_mutex_t*)arg); } void* thread_func(void* arg) { pthread_cleanup_push(cleanup, &mutex); pthread_mutex_lock(&mutex); if (error_occurred) goto exit; // 触发清理函数 // 临界区操作 exit: pthread_cleanup_pop(1); // 1表示执行清理函数 return NULL; } ``` 2. **局部变量有效性** `goto`只能在同一函数内跳转,且不可跨越变量初始化语句。 ```c void* thread_func(void* arg) { int a = 10; label: int b = 20; // 错误!跳转到label会跳过b的初始化 // ... goto label; } ``` 3. **线程安全性** - 若`goto`涉及共享数据(如全局变量、堆内存),需通过**互斥锁**保护操作[^1]。 - 避免在`goto`路径中遗留部分修改的共享状态(需保证原子性)。 --- #### **3. 替代方案建议** - **使用`return`或`break`替代**:优先通过函数返回值或循环控制简化逻辑。 - **结构化错误处理**:封装资源管理(如RAII模式,C++中利用析构函数自动释放资源)。 - **有限状态机**:用状态变量替代复杂的跳转逻辑。 --- #### **4. 总结** - **合理使用`goto`**:仅在简化错误处理时使用,避免滥用破坏代码可读性。 - **线程安全优先**:结合互斥锁和清理函数确保资源安全[^2][^3]。 - **代码审查**:多线程代码需严格检查`goto`路径的完整性和安全性。 --- ### 相关问题 1. 如何通过`setjmp`/`longjmp`实现跨函数跳转? 2. 多线程环境下如何安全地使用线程局部存储(TLS)? 3. 如何利用C++的RAII机制替代`goto`的错误处理?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值