Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Varios (https://www.clubdelphi.com/foros/forumdisplay.php?f=11)
-   -   Quitar dos espacios en cadena dejar uno (https://www.clubdelphi.com/foros/showthread.php?t=89182)

escafandra 15-10-2015 02:19:03

Cita:

Empezado por ecfisa (Mensaje 497950)
Si, siempre lo entendí del mismo modo (de allí que empecé las pruebas extendiendo la cadena un espacio mas).
Pero contra toda predicción, no hubo advertencia ni errores al acceder a una posición mas allá del valor de Length, eso me dejó con dudas e hice múltiples pruebas sin lograr que se produzca advertencia o error alguno. De ahí que atribuí que obtenía ese resultado por tratar con un AnsiString.

Realmente siempre puse cuidado en no acceder a una posición mayor al largo de una cadena pensando en que generaría un error, pero todas las pruebas realizadas hasta ahora me indican que, al menos acceder (lectura) a una posición mas del valor devuelto por Length de un AnsiString no genera error.

¿ Conoces algún caso en que se produzca ?

Saludos :)

En un array podemos definir por código un índice que se salga del rango:

Código Delphi [-]
var
  S: array [0..0] of char;
  i: integer;
begin
  i:= 10;
  S[i]:= #0;
end;

En ese caso, debemos velar por que no suceda o tener bien controlado lo que hacemos. La escritura fuera de los límites puede tener consecuencias y el compilador no avisa.

Saludos

mamcx 15-10-2015 03:50:21

Ocurren el error en tiempo de ejecucion, porque hacerlo en tiempo de compilacion es un problema particularmente espinoso:

https://duckduckgo.com/?q=compile+time+bounds+checking

Luego esta el hecho que el indice es un Int(32/64) que va desde el - al + en la escala numerica, en vez desde 0-+ (osea: El tipo de datos no es el indicado para un indice de array posicional). Luego el que String/Array son dinamicos, y detectar que no se pase un indice IMPLICA detectar cuando se redimensiona el array... lo que basicamente implica tener que correr el codigo ;) (Hay tecnicas que se pueden usar, llamadas "data-flow", pero son muy complejas de implementar si se quiere una solucion GENERICA).

La solucion? En primer lugar habria que arrancar con dejar de usar INTs y usar un tipo de datos mas adecuado. Luego, usar un sub-rango cuando el tamño maximo es conocido (ej: Si indexamos meses y sabemos que no son mas de 12). Pero cuando el tamaño es indeterminado? Igual toca comprobar en tiempo de ejecucion

ecfisa 15-10-2015 04:59:47

Hola escafandra.

Cita:

La escritura fuera de los límites puede tener consecuencias y el compilador no avisa.
Si, comprendo la implicancia que tiene escribir fuera de los límites de un arreglo y estamos totalmente de acuerdo en ese punto, sólo que en este caso se trata de una lectura.

Por ejemplo, se puede correr este código una y otra vez sin ningún tipo de problemas ni mensajes (excepto el de finalización)
Código Delphi [-]
var
  i: Char;
  j: Integer;
  s, x: string;
begin
  ListBox1.Items.BeginUpdate;
  try
    for i := 'A' to 'Z' do
      for j:= 1 to 10000000 do
      begin
        s := s + i;
        x := s[Length(s)+1]; //  lectura en: [Length(s) + 1] 
        if x = ' ' then ListBox1.Items.Add(i);
      end;
  finally
    ListBox1.Items.EndUpdate;
  end;
  ShowMessage('fin');
end;
Siempre puse especial cuidado en no escribir en "zonas desconocidas", ya que no hay dudas sobre sus potenciales y nefastas consecuencias (reitero que mi duda no va por ese lado). Pero tenía el mismo concepto para con la lectura, por lo que nunca antes se me ocurrió probar y me resulta sorprendente el resultado de las pruebas.

Saludos :)

Casimiro Notevi 15-10-2015 09:41:32

Cita:

Empezado por ecfisa (Mensaje 497972)
me resulta sorprendente el resultado de las pruebas.

Realmente curioso. Creía que con delphi se estaba "protegido" ante esas problemáticas.

escafandra 15-10-2015 12:32:32

Cita:

Empezado por ecfisa (Mensaje 497972)
Si, comprendo la implicancia que tiene escribir fuera de los límites de un arreglo y estamos totalmente de acuerdo en ese punto, sólo que en este caso se trata de una lectura.

En realidad puedes leer o escribir dependiendo del tipo de protección de memoria establecido.

Cita:

Empezado por Casimiro Notevi (Mensaje 497979)
Realmente curioso. Creía que con delphi se estaba "protegido" ante esas problemáticas.

Delphi no puede estar protegido contra esto pues muchas estructuras de windows definen un elemento de un array de dimensión desconocida y dada por el tamaño de la estructura, conocido más tarde.


Saludos.

Casimiro Notevi 15-10-2015 13:48:44

Cita:

Empezado por escafandra (Mensaje 497981)
Delphi no puede estar protegido contra esto pues muchas estructuras de windows definen un elemento de un array de dimensión desconocida y dada por el tamaño de la estructura, conocido más tarde.
Saludos.

Pero mientras no se sepa qué dimensión tiene, debería ser null y no dejar acceder.

escafandra 15-10-2015 14:27:38

Cita:

Empezado por Casimiro Notevi (Mensaje 497983)
Pero mientras no se sepa qué dimensión tiene, debería ser null y no dejar acceder.


Te pongo un ejemplo típico:

La estructura WLAN_AVAILABLE_NETWORK_LIST es usada para obtener una lista de redes wifi disponiobles, la API WlanGetAvailableNetworkList recibe por referencia un puntero a un elemento WLAN_AVAILABLE_NETWORK_LIST y devuelve la lista deseada en un bloque de memoria a la que apunta el puntero pasado.
Esta es la definición:
Código Delphi [-]
TWLAN_AVAILABLE_NETWORK_LIST = record
    dwNumberOfItems: DWORD;
    dwIndex: DWORD;
    Network: array[0..0] of TWLAN_AVAILABLE_NETWORK;
end;
Observa que el array Network definido en la estructura solo contiene un elemento, sin embargo puede tener muchos mas. Cuando WlanGetAvailableNetworkList nos devuelva el puntero a la lista de redes (WLAN_AVAILABLE_NETWORK_LIST), Windows informa del número de elementos que tiene en dwNumberOfItems. Nosotros navegaremos por el array Network donde están las redes, el límite lo controlaremos nosotros pero no delphi.

Casos como el que expongo son habituales cuando se trabaja a nivel de API. Un compilador que limite los índices no sería operativo a bajo nivel y delphi lo es.


Saludos.

Casimiro Notevi 15-10-2015 16:18:12

Sí. te entiendo, no había pensado en esos casos.
Aunque puestos a buscar soluciones seguras podrían usar otro forma, por ejemplo un array dinámico o alguna otra solución.

ecfisa 15-10-2015 16:34:28

Hola.

Con el fin de entender esto, me puse a investigar un poco. Y de lo conocido, mas lo que pude ampliar leyendo, una variable de tipo AnsiString contiene un apuntador que almacena un valor nulo si está vacía o de modo contrario apunta a la dirección del comienzo de una cadena finalizada en nulo al estilo de las cadenas C.

Pero no termina allí, el formato en que se almacena este tipo es:
Código:

[contador de referencias] [longitud de cadena] [cadena + nulo]
El contador de referencias mantiene la cantidad de variables que apuntan a esa cadena en un momento determinado. La longitud de la cadena es almacenada de forma similar a como lo hacía Pascal y por último sigue la cadena en si misma, finalizada en nulo como en C. Lo que hace que este tipo sea una especie de tipo híbrido entre Pascal y C.

El contador de referencias se incrementa cada vez que una cadena es asignada a una variable y se decrementa cuando deja de hacerlo. De este modo cuando el contador llega a cero la memoria previamente reservada es liberada de forma automática; del mismo modo es liberada cuando sale de su ámbito (al estilo de C++). Las variables de este tipo también se inicializan como cadenas vacías de forma automática.

Por último cuando se concatena carácter a carácter, es posible que se libere y reasigne memoria en cada asignación. De lo que resulta, por ejemplo, que es mas eficiente hacer:
Código Delphi [-]
  SetLength(s, 15);
  FillChar(s[1], 15, Ord(' '));
que:
Código Delphi [-]
  for i := 1 to 15 do str := str + ' ';

Haciendo unas pruebas pude pude lograr que se produzca error que buscaba:
Código Delphi [-]
procedure foo(const str: string);
var
  s: string;
  i: Integer;
begin
  for i := 1 to 100 do s := s + str[i];
end;

// Llamada que funciona
procedure TForm1.btnWithoutErrorClick(Sender: TObject);
var
  str: string;
begin
  str := #0;  // o inicializada con cualquier caracter
  foo(str);
end;

// Llamada que produce error
procedure TForm1.btnWithErrorClick(Sender: TObject);
var
  str: string;
begin
  str := EmptyStr; // u omitiendo la línea
  foo(str);
end;
y como se puede ver, sucede cuando el argumento no fue previamente inicializado.

Por último, la función del mensaje #9, resistió todas mis pruebas sin generar ningún error.

En definitiva, no encontré la alusión concreta que buscaba, pero sé un poco mas al respecto :).

Saludos :)

roman 15-10-2015 17:40:01

Estoy confundido con sus confusiones :D

¿Cómo está eso de que "Creía que con delphi se estaba "protegido" ante esas problemáticas."?

Por un lado, como ya comentó escafandra, delphi permite el acceso fuera del rango especificado por un arreglo para datos a los que se quiere acceder de esa forma pero que no se conocen sino hasta la ejecución. Pero, por otro lado, ¿les suena conocido eso de {$R+}? Esa directiva al compilador impide tal acceso, así que sí: delphi nos protege hasta de eso y poder acceder fuera de rango hay que avisarle {$R-} que no queremos que nos proteja :).

Por otro lado, leer más allá de los índices posiblemente no de errores pero debe hacerse siempre y cuando sepamos qué vamos a leer. En el caso presente, debíamos saber que en Length+1 no había un espacio y eso no podíamos asegurarlo si no lo asignábamos nosotros mismos.

Finalmente, ya había mencionado porqué cambia el comportamiento cuando se asigna un valor a Memo1.Text caracter a caracter o de un sólo golpe. La asignación a la propiedad Text implica el envio del mensaje WM_TEXT al control, que eventualmente manejará la API de Windows. Si bien el string de delphi no tiene problemas para manejar #0's, la API de Windows cortará cualquier cadena que se envie en el primero #0 que vea. Pero cuando lo hacemos caracter por caracter, si llegaos a un #0, ése no aparecerá en el memo, pero como seguimos avanzando, sí aparecerá cualquier otro caracter que no sea #0.

// Saludos

Casimiro Notevi 15-10-2015 18:02:56

Cita:

Empezado por roman (Mensaje 497999)
Estoy confundido con sus confusiones :D

Puede que esté confundido por el cambio de lenguaje :confused:

AgustinOrtu 15-10-2015 20:53:01

Roman, Daniel y Casimiro confundidos? Mejor mantener las narices lejos :D

Casimiro Notevi 15-10-2015 20:55:49

Los años no perdonan :p:D

ecfisa 15-10-2015 23:17:27

Hola.

Cita:

Empezado por roman (Mensaje 497999)
Por otro lado, leer más allá de los índices posiblemente no de errores pero debe hacerse siempre y cuando sepamos qué vamos a leer. En el caso presente, debíamos saber que en Length+1 no había un espacio y eso no podíamos asegurarlo si no lo asignábamos nosotros mismos.

La lógica me indicaba (y me indica) opinar igual que vos cuando hiciste esa observación en los primeros mensajes y pensé que el código del mensaje #9, no había fallado en las pruebas por motivos de suerte (de no haber encontrado un espacio mas allá del fin de la cadena).

Para salir de toda duda hice una pequeña prueba y las sorpresas no terminaron.
Código Delphi [-]
const
  ABC = 'ABCDEFGHIJKLMNOPQRSTVUWXYZ';

procedure TForm1.Button1Click(Sender: TObject);
var
  s   : AnsiString;
  i,j : Integer;
  TS  : TStrings;
begin
  Randomize;
  TS := TStringList.Create;
  try
    TS.BeginUpdate;
    for i := 1 to 1000 do
    begin
      s:= '';
      for j := 1 to Random(Length(ABC))+1 do
        s := s + abc[Random(Length(ABC))+1];
      TS.Add(Format('%.3d: %s',[Ord(s[Length(s)+1]), s]));
    end;
    TS.SaveToFile('prueba.txt');
  finally
    TS.EndUpdate;
    TS.Free;
  end;
  ShowMessage('fin');
end;
Al revisar el archivo prueba.txt veo que en todos los casos la primera posición mas allá del último caracter de un AnsiString contiene el valor cero.

Me dije "suerte otra vez... vamos a seguir probando pero trabajando menos":
Código Delphi [-]
function HaveDistinctZero(const Laps: Integer): Integer;
const
  ABC = 'ABCDEFGHIJKLMNOPQRSTVUWXYZ';
var
  s: AnsiString;
  i, j: Integer;
begin
  Result := 0;
  for i := 1 to Laps do
  begin
    s:= '';
    for j := 1 to Random(Length(ABC))+1 do
      s := s + abc[Random(Length(ABC))+1];
    if Ord(s[Length(s)+1]) <> 0 then Inc(Result);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowMessage(IntToStr(HaveDistinctZero(100000000))); // resultado: 0
end;
Corrí la prueba tres veces y resultó que en los cientos de millones de casos, siempre obtuve el mismo resultado: La primera posición despues del último caracter de un AnsiString tiene el valor cero (no controlé mas allá).

O no estoy haciendo las pruebas correctas o Delphi está inicializando el espacio contiguo de algún modo.

Sigo sin encontrar la explicación...

Saludos :)

escafandra 16-10-2015 00:57:55

Cita:

Empezado por ecfisa (Mensaje 497995)
Con el fin de entender esto, me puse a investigar un poco. Y de lo conocido, mas lo que pude ampliar leyendo, una variable de tipo AnsiString contiene un apuntador que almacena un valor nulo si está vacía o de modo contrario apunta a la dirección del comienzo de una cadena finalizada en nulo al estilo de las cadenas C.

Cita:

Empezado por ecfisa (Mensaje 498020)
Corrí la prueba tres veces y resultó que en los cientos de millones de casos, siempre obtuve el mismo resultado: La primera posición despues del último caracter de un AnsiString tiene el valor cero (no controlé mas allá).

O no estoy haciendo las pruebas correctas o Delphi está inicializando el espacio contiguo de algún modo.

Sigo sin encontrar la explicación...

Tu mismo te diste la respuesta. Las cadenas en delphi terminan como en C, con un nulo, es por eso la facilidad del cast PCHAR. PCHAR te devuelve una cadena estilo C, si te fijas en el ejemplo que puse en asm, termino la cedena con un nulo y la conversión a String es perfecta, si no lo haces, la cadena resultante será tan larga como la original y contiene el resto de la cadena tras retirarle los espacios múltiples:
"Hola que tal" se transforma en "Hola que tal" si metes el nulo final, en caso contrario será "Hola que tal tal". ¿Porqué? porque la cadena original terminaba en nulo.
De esta forma, el cast PCHAR(Cadena) es lo mismo que hacer @Cadena[1], que es justo el casting que realizo para tratarla desde ensamblador.

Te sorprenderías al saber la cantidad de analogías existentes entre delphi y C a bajo nivel, por ejemplo, los arrays se tratan idénticamente. Si tienes curiosidad, sigue este hilo donde hicimos una investigación sobre el tema, y este otro hilo que demuestra lo mismo.


Saludos.

roman 16-10-2015 01:38:55

Cita:

Empezado por ecfisa (Mensaje 498020)
o Delphi está inicializando el espacio contiguo de algún modo.

Es posible que así sea. De todas formas, hay que recordar que en delphi, un tipo string, más que una cadena de texto, es una cadena de bytes y puede almacenar cualquier cosa, incluyendo el #0. Por tanto, confiar en que el #0 marca el final de un string es incorrecto.

// Saludos

ecfisa 16-10-2015 01:48:16

Hola escafandra.

Si conocía algunas de las similitudes y el manejo de las conversiones, pero el árbol me tapó el bosque :o.
¡ Tenía la respuesta ahí nomas !, como dicen por aca: "Si hubiera sido una víbora me picaba"...
Cita:

Tu mismo te diste la respuesta. Las cadenas en delphi terminan como en C,...
Leer otra vez ese texto me hizo caer en el por qué del 0 en la posición Length + 1.

Declarando como AnsiString la cadena que usaste,
Código Delphi [-]
var
   s: AnsiString = 'Hola que tal';
sería almacenada así:
Código:

[1][12][Hola que tal\0]
De ese modo s[Length(s)+1], es decir 13 en este caso, apunta al nulo que finaliza la cadena y por lo mismo nunca encontré un espacio en esa posición.

Saludos y muchas gracias :)


La franja horaria es GMT +2. Ahora son las 21:30:54.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi