리눅스 스타일 가이드는 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;
}
우리가 goto
s 없이 동등한 것을 고려한다면 :
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_free
goto out 스타일이 그렇게 실용적인 이유 입니다.
프로그래머 goto
가 사악 하다고 믿어야한다고 생각합니다 . 그런 다음 충분히 성숙 해지면 Linux 소스 코드를 탐색하고 Linux 스타일 가이드를 읽어야합니다.
나는이 스타일을 매우 일관되게 사용하고 모든 함수에는 int retval
, out_free
label 및 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 루프가 끊어 지거나 계속 될 때마다 기분이 나쁘다는 것을 알았습니다.