리눅스 스타일 가이드는 goto예제와 일치하는를 사용해야하는 특별한 이유를 제공 합니다.
https://www.kernel.org/doc/Documentation/process/coding-style.rst
gotos를 사용하는 이유는 다음과 같습니다.
- 무조건 진술은 이해하고 따르기가 더 쉽다
- 중첩이 줄어 듭니다
- 수정시 개별 종료점을 업데이트하지 않아 오류 발생
- 중복 코드를 최적화하기 위해 컴파일러 작업을 절약합니다.)
면책 조항 나는 내 일을 공유해서는 안됩니다. 여기 예제는 약간 고려되어 있으므로 곰이 나와 함께 견뎌주십시오.
메모리 관리에 좋습니다. 나는 최근에 동적으로 메모리를 할당 한 코드 (예 char *: 함수에 의해 반환 된 코드)를 연구했습니다. 경로를보고 경로의 토큰을 구문 분석하여 경로가 유효한지 확인하는 함수입니다.
tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
...
some statements, some involving dynamically allocated memory
...
if ( check_this() ){
free(var1);
free(var2);
...
free(varN);
return 1;
}
...
some more stuff
...
if(something()){
if ( check_that() ){
free(var1);
free(var2);
...
free(varN);
return 1;
} else {
free(var1);
free(var2);
...
free(varN);
return 0;
}
}
token = strtok(NULL,delim);
}
free(var1);
free(var2);
...
free(varN);
return 1;
이제 나에게 다음 코드를 추가 해야하는 경우 훨씬 훌륭하고 유지 관리가 쉽습니다 varNplus1.
int retval = 1;
tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
...
some statements, some involving dynamically allocated memory
...
if ( check_this() ){
retval = 1;
goto out_free;
}
...
some more stuff
...
if(something()){
if ( check_that() ){
retval = 1;
goto out_free;
} else {
retval = 0;
goto out_free;
}
}
token = strtok(NULL,delim);
}
out_free:
free(var1);
free(var2);
...
free(varN);
return retval;
이제 코드에는 N과 관련하여 다른 문제가 있습니다. 즉, N은 10 이상이고 함수는 450 개가 넘었으며 10 개 수준의 중첩이 있습니다.
그러나 나는 관리자에게 리팩토링을 제안했는데, 내가 한 일이 이제는 짧고 기능은 모두 리눅스 스타일입니다.
int function(const char * param)
{
int retval = 1;
char * var1 = fcn_that_returns_dynamically_allocated_string(param);
if( var1 == NULL ){
retval = 0;
goto out;
}
if( isValid(var1) ){
retval = some_function(var1);
goto out_free;
}
if( isGood(var1) ){
retval = 0;
goto out_free;
}
out_free:
free(var1);
out:
return retval;
}
우리가 gotos 없이 동등한 것을 고려한다면 :
int function(const char * param)
{
int retval = 1;
char * var1 = fcn_that_returns_dynamically_allocated_string(param);
if( var1 != NULL ){
if( isValid(var1) ){
retval = some_function(var1);
} else {
if( isGood(var1) ){
retval = 0;
}
}
free(var1);
} else {
retval = 0;
}
return retval;
}
나에게, 첫 번째 경우, 첫 번째 함수 NULL가을 반환 하면 여기서 나가서 반환한다는 것이 분명합니다 0. 두 번째 경우에는 if가 전체 함수를 포함하는지 확인하기 위해 아래로 스크롤해야합니다. 첫 번째는 이것이 나에게 문체 적으로 (이름 " out") 표시하고 두 번째는 구문 적으로 표시합니다. 첫 번째는 여전히 더 분명합니다.
또한 free()함수 끝에 선언문을 사용하는 것이 좋습니다. 그것은 내 경험상 free()함수의 중간에있는 문장이 나쁜 냄새를 맡고 서브 루틴을 만들어야한다는 것을 나타 내기 때문입니다. 이 경우, 나는 var1내 기능으로 만들었고 free()서브 루틴에서 그것을 할 수 없었 습니다. 그러나 그것이 goto out_freegoto out 스타일이 그렇게 실용적인 이유 입니다.
프로그래머 goto가 사악 하다고 믿어야한다고 생각합니다 . 그런 다음 충분히 성숙 해지면 Linux 소스 코드를 탐색하고 Linux 스타일 가이드를 읽어야합니다.
나는이 스타일을 매우 일관되게 사용하고 모든 함수에는 int retval, out_freelabel 및 out 레이블 이 있다고 덧붙여 야합니다 . 스타일 일관성으로 인해 가독성이 향상되었습니다.
보너스 : 휴식과 계속
while 루프가 있다고 가정 해보십시오.
char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
var1 = functionA(line,count);
var2 = functionB(line,count);
if( functionC(var1, var2){
count++
continue;
}
...
a bunch of statements
...
count++;
free(var1);
free(var2);
}
이 코드에는 다른 문제가 있지만, 한 가지는 continue 문입니다. 전체 내용을 다시 작성하고 싶지만 작은 방식으로 수정해야했습니다. 나를 만족시키는 방식으로 리팩토링하는 데 며칠이 걸렸지 만 실제 변화는 약 반일의 작업이었습니다. 문제는 우리가 '경우에도 것입니다 continue우리가 아직 확보해야' var1하고 var2. 나는을 추가 var3해야했고, free () 문을 반영하기 위해 노력하고 싶었다.
나는 당시에 비교적 새로운 인턴이지만, 잠시 동안 리눅스 소스 코드를보고 있었기 때문에 관리자에게 goto 문을 사용할 수 있는지 물었다. 그는 그렇다고 말했습니다.
char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
var1 = functionA(line,count);
var2 = functionB(line,count);
var3 = newFunction(line,count);
if( functionC(var1, var2){
goto next;
}
...
a bunch of statements
...
next:
count++;
free(var1);
free(var2);
}
나는 계속되는 것이 최선이라고 생각하지만 나에게는 그들은 보이지 않는 레이블이있는 goto와 같습니다. 휴식도 마찬가지입니다. 여기서와 마찬가지로 여러 위치에서 수정 사항을 미러링하도록 강요하지 않는 한 계속 또는 중단을 선호합니다.
또한이 사용 goto next;과 next:레이블이 만족스럽지 않다고 덧붙여 야합니다 . free()의 count++진술 과 진술을 반영하는 것보다 낫습니다 .
goto의 경우는 거의 항상 잘못된 것이지만 사용하기 좋은시기를 알아야합니다.
내가 논의하지 않은 한 가지는 다른 답변에 의해 다루어 진 오류 처리입니다.
공연
strtok ()의 구현을 볼 수 있습니다 http://opensource.apple.com//source/Libc/Libc-167/string.subproj/strtok.c
#include <stddef.h>
#include <string.h>
char *
strtok(s, delim)
register char *s;
register const char *delim;
{
register char *spanp;
register int c, sc;
char *tok;
static char *last;
if (s == NULL && (s = last) == NULL)
return (NULL);
/*
* Skip (span) leading delimiters (s += strspn(s, delim), sort of).
*/
cont:
c = *s++;
for (spanp = (char *)delim; (sc = *spanp++) != 0;) {
if (c == sc)
goto cont;
}
if (c == 0) { /* no non-delimiter characters */
last = NULL;
return (NULL);
}
tok = s - 1;
/*
* Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
* Note that delim must have one NUL; we stop if we see that, too.
*/
for (;;) {
c = *s++;
spanp = (char *)delim;
do {
if ((sc = *spanp++) == c) {
if (c == 0)
s = NULL;
else
s[-1] = 0;
last = s;
return (tok);
}
} while (sc != 0);
}
/* NOTREACHED */
}
내가 틀렸다면 정정하십시오. 그러나 cont:레이블과 goto cont;문장이 성능을 위해 있다고 생각합니다 (확실히 코드를 읽을 수있게하지는 않습니다). 이렇게하면 읽을 수있는 코드로 대체 될 수 있습니다
while( isDelim(*s++,delim));
구분자를 건너 뛰려면 그러나 가능한 빨리하고 불필요한 함수 호출을 피하기 위해이 방법을 사용합니다.
나는 Dijkstra의 논문을 읽었고 나는 매우 난해한 것을 발견했다.
Google은 2 개 이상의 링크를 게시 할만큼 평판이 충분하지 않기 때문에 "dijkstra goto 문이 유해한 것으로 간주됩니다".
나는 그것이 goto를 사용하지 않는 이유라고 인용 한 것을 보았고 그것을 읽는 것이 나의 goto를 사용하는 한 아무것도 변하지 않았다.
부록 :
나는이 모든 것에 대해 계속 생각하고 깨는 동안 깔끔한 규칙을 생각해 냈습니다.
- while 루프에 continue가 있으면 while 루프의 본문은 함수 여야하며 continue는 return 문입니다.
- while 루프에 break 문이 있으면 while 루프 자체가 함수 여야하며 break가 return 문이되어야합니다.
- 둘 다 가지고 있다면 무언가 잘못되었을 수 있습니다.
범위 문제로 인해 항상 가능한 것은 아니지만이 작업을 수행하면 코드에 대해 추론하기가 훨씬 쉽다는 것을 알았습니다. 나는 while 루프가 끊어 지거나 계속 될 때마다 기분이 나쁘다는 것을 알았습니다.