2010-07-14 12 views
25

He estado depurando un problema con una función que devuelve una cadena que me preocupa. Siempre he asumido que la variable de resultado implícito para las funciones que devuelven una cadena estaría vacía al inicio de la llamada de función, pero el (simplificado) siguiente código producido un resultado inesperado:¿Inicia el resultado de la función de cadena?

function TMyObject.GenerateInfo: string; 

     procedure AppendInfo(const AppendStr: string); 
     begin 
      if(Result > '') then 
      Result := Result + #13; 
      Result := Result + AppendStr; 
     end; 

begin 
    if(ACondition) then 
    AppendInfo('Some Text'); 
end; 

Al llamar a este múltiple función veces resultaron en:

"Some Text" 

primera vez,

"Some Text" 
"Some Text" 

el segundo tiempo,

"Some Text" 
"Some Text" 
"Some Text" 

tercera vez, etc.

Para solucionarlo tuve que inicializar el Resultado:

begin 
    Result := ''; 
    if(ACondition) then 
    AppendInfo('Some Text'); 
end; 

¿Es necesario inicializar un resultado de la función de cuerdas? ¿Por qué (técnicamente)? ¿Por qué el compilador no emite una advertencia "W1035 El valor de retorno de la función 'xxx' podría estar indefinido" para las funciones de cadena? ¿Debo revisar todo mi código para asegurarme de que se establece un valor, ya que no es confiable esperar una cadena vacía de una función si el resultado no está establecido explícitamente?

He probado esto en una nueva aplicación de prueba y el resultado es el mismo.

procedure TForm1.Button1Click(Sender: TObject); 
var 
    i: integer; 
    S: string; 
begin 
    for i := 1 to 5 do 
    S := GenerateInfo; 
    ShowMessage(S); // 5 lines! 
end; 
+0

Experimenté algo similar con una función que devuelve una interfaz. Parece que hay un problema general con los tipos de datos que requieren inicialización. No he intentado arreglos dinámicos pero supongo que también mostraría este problema. – dummzeuch

+0

+1 para una muy buena pregunta. –

Respuesta

28

Este no es un error, pero "feature":

Para una cadena, matriz dinámica, método puntero, o resultado variante, los efectos son los mismos que si el resultado función eran declarado como var parámetro adicional siguiendo los parámetros declarados . En otras palabras, la persona que llama pasa un puntero adicional de 32 bits que apunta a una variable en que devuelve el resultado de la función.

I.e. su

function TMyObject.GenerateInfo: string; 

¿Es realmente esto:

procedure TMyObject.GenerateInfo(var Result: string); 

Nota "var" prefijo (no "cabo" como se puede esperar!).

Esto es TES no intuitivo, por lo que conduce a todo tipo de problemas en el código. Código en cuestión: solo un ejemplo de los resultados de esta función.

Ver y votar por this request.

+4

No puede de buena fe llamar a esto una característica. Misfeature, quizás: http://catb.org/jargon/html/M/misfeature.html. Pero un +1 definitivo para el enlace a los documentos. –

+6

No creo que ninguna de las consecuencias sea poco intuitiva. Siempre he considerado 'result' como una variable local, y estos generalmente (enteros, reales, booleanos, ...) deben inicializarse manualmente. Por lo tanto, reacciono con bastante fuerza al código del OP que carece del 'resultado: = '' 'primera línea. –

+0

*> Siempre he considerado el resultado como una variable local * que es ** no **. El resultado es el argumento de salida de la función. No es una variable local. – Alex

0

Parece un error en D2007. Acabo de probarlo en Delphi 2010 y obtuve el comportamiento esperado. (1 línea en lugar de 5.)

+0

Acabo de probarlo en Delphi 2009, y obtuve las cinco líneas. –

+0

No es un error - ver mi respuesta. – Alex

+2

¿Pero entonces es un error con D2010? –

4

Nos hemos topado con esto antes, creo que ya desde Delphi 6 o 7. Sí, aunque el compilador no se molesta en advertirte, lo haces necesita inicializar su cadena Variables de resultado, precisamente por la razón que se encontró. La variable de cadena es inicializada - no comienza como una referencia de basura - pero parece que no se obtiene re inicializada cuando se espera que lo haga.

En cuanto a por qué sucede ... no estoy seguro. Es un error, por lo que no necesariamente necesita una razón. Solo lo vimos suceder cuando llamamos repetidamente a la función en un bucle; si lo llamamos fuera de un bucle, funcionó como se esperaba. Parecía que el llamador estaba asignando espacio para la variable de resultado (y reutilizándolo cuando llamó a la misma función repetidamente, causando el error), en lugar de la función asignando su propia cadena (y asignando una nueva en cada llamada).

Si usaba cadenas cortas, la persona que llama asigna el búfer, es un comportamiento tradicional para tipos de valores grandes. Pero eso no tiene sentido para AnsiString. Quizás el equipo compilador simplemente olvidó cambiar la semántica cuando implementaron por primera vez cadenas largas en Delphi 2.

+2

La persona que llama no asigna espacio pero pasa una referencia válida a una variable inicializada, al menos la concepción general es la que yo entiendo. Tiene informes: http://qc.embarcadero.com/wc/qcmain.aspx?d=894, http://qc.embarcadero.com/wc/qcmain.aspx?d=32556 probablemente algunos otros también. –

+1

No es un error - ver mi respuesta. – Alex

0

Si crees que se realizan algunas gestiones automáticas de cadenas para hacerte la vida más fácil, en parte tienes razón. Todas estas cosas también se hacen para que la lógica de la cadena sea coherente y los efectos secundarios libres.

En muchos lugares hay cadenas pasadas por referencia, pasadas por valor, pero todas estas líneas esperan cadenas VALIDAS en cuyo contador de administración de memoria hay algún valor válido, no es basura. Por lo tanto, para mantener las cadenas de caracteres válidas, lo único seguro es que deberían inicializarse cuando se introdujeron por primera vez. Por ejemplo, para cualquier cadena variable local, esta es una necesidad ya que este es el lugar donde se introduce una cadena. El resto del uso de cadenas, incluida la función(): cadena (que en realidad procedimiento (var Resultado: cadena) como Alexander señaló correctamente) solo espera cadenas válidas en la pila, no inicializadas. Y la validez aquí viene del hecho de que la construcción (var Result: string) dice que "estoy esperando una variable válida que definitivamente se introdujo antes". ACTUALIZACIÓN: Debido a que el contenido real de Resultado es inesperado, pero debido a la misma lógica, si es la única llamada a esta función que tiene una variable local a la izquierda, el vacío de la cadena en este caso está garantizado.

2

Esto no es un error. Por definición, no se inicializa ninguna variable dentro de la función, incluido el resultado.

Por lo tanto, su resultado es undefind en la primera llamada, y puede contener cualquier cosa. Cómo se implementa en el compilador es irrelevante, y puede tener diferentes resultados en diferentes compiladores.

1

Parece que su función debería simplificarse así:

function TMyObject.GenerateInfo: string; 
begin 
    if(ACondition) then 
    Result := 'Some Text' 
    else 
    Result := ''; 
end; 

Por lo general, no quiere usar resultado en el lado derecho de una asignación de una función.

De todos modos, estrictamente para fines ilustrativos, se podría también hacer esto, aunque no se recomienda: Respuesta

procedure TForm1.Button1Click(Sender: TObject); 
var 
    i: integer; 
    S: string; 
begin 
    for i := 1 to 5 do 
    begin 
    S := ''; // Clear before you call 
    S := GenerateInfo; 
    end; 
    ShowMessage(S); // 5 lines! 
end; 
0

de Alex es casi siempre correcto y respuestas ¿Por qué estaba viendo el comportamiento extraño que yo era, pero no es toda la historia

Lo siguiente, compilado sin optimización, produce el resultado esperado de que sTemp sea una cadena vacía. Si cambia la función para la llamada de procedimiento, obtendrá un resultado diferente.

Parece haber una regla diferente para la unidad de programa real.

Es cierto que este es un caso de esquina.

program Project1; 

{$APPTYPE CONSOLE} 

uses System.SysUtils; 

    function PointlessFunction: string; 
    begin 
    end; 

    procedure PointlessProcedure(var AString: string); 
    begin 
    end; 

var 
    sTemp: string; 
begin 
    sTemp := '1234'; 
    sTemp := PointlessFunction; 
    //PointlessProcedure(sTemp); 
    WriteLn('Result:' + sTemp); 
    ReadLn; 
end. 
Cuestiones relacionadas