- Problema de Combinatoria: Disposición Familiar
Imaginemos tres pares de padres e hijos. Si se paran en una fila, y cada padre e hijo de la misma familia no pueden estar adyacentes (es decir, el padre A no puede estar junto al hijo A, etc.), ¿cuántas disposiciones diferentes son posibles?
- 120
- 48
- 240
- 144
Respuesta: C.
Análisis de Solución:
Método 1: Construcción Paso a Paso
Sean los tres pares (P1, H1), (P2, H2), (P3, H3). En total, hay 6 personas.
- El primer puesto puede ser ocupado por cualquiera de las 6 personas.
- Para el segundo puesto, si el primero fue P1, el segundo no puede ser H1. Esto deja 4 opciones (P2, H2, P3, H3).
- A partir de aquí, la situación se ramifica:
- **Caso A:** Si la tercera persona es el hijo del primero (por ejemplo, H1 después de P1), entonces las tres personas restantes deben cumplir las restricciones. Hay solo 2 formas de organizar los dos pares restantes para que los hijos no estén junto a sus padres (P2 H3 P3 H2 o P3 H2 P2 H3, y similarmente para las otras permutaciones de los dos pares restantes).
- **Caso B:** Si la tercera persona NO es el hijo del primero (por ejemplo, P1 _ P2), entonces tenemos 2 opciones (P2 o P3). Luego, las 3 personas restantes se pueden organizar de 4 maneras para cumplir las restricciones.
Esto lleva a un cálculo de: 6 * 4 * (2 + 2 * 4) = 240.
Método 2: Principio de Inclusión-Exclusión
El número total de permutaciones de 6 personas es P(6,6) = 6! = 720.
- Calcular el total de permutaciones - (una pareja adyacente) + (dos parejas adyacentes) - (tres parejas adyacentes).
- Una pareja adyacente: Elegimos 1 de 3 parejas (C(3,1)). Consideramos la pareja como una unidad (2! formas internas). Las 5 "unidades" (la pareja + 4 individuos) se permutan de 5! formas. Total: C(3,1) * 2! * 5! = 3 * 2 * 120 = 720.
- Dos parejas adyacentes: Elegimos 2 de 3 parejas (C(3,2)). Cada pareja es una unidad (2! * 2! formas internas). Las 4 "unidades" se permutan de 4! formas. Total: C(3,2) * (2!)^2 * 4! = 3 * 4 * 24 = 288.
- Tres parejas adyacentes: Elegimos 3 de 3 parejas (C(3,3)). Cada pareja es una unidad ((2!)^3 formas internas). Las 3 "unidades" se permutan de 3! formas. Total: C(3,3) * (2!)^3 * 3! = 1 * 8 * 6 = 48.
Aplicando el principio: 720 - 720 + 288 - 48 = 240.
Método 3: Enfoque Modular
- Selecciona dos pares de familias de las tres disponibles: C(3,2) = 3 formas.
- Organiza estas cuatro personas de modo que ningún padre e hijo estén juntos. Hay 2 * 2 = 4 formas (ejemplo: P1 P2 H1 H2, P1 H2 P2 H1, etc., considerando permutaciones de pares y dentro de pares).
- La pareja restante (dos personas) se puede insertar en los 5 "espacios" entre las 4 personas ya colocadas. Esto es P(5,2) = 5 * 4 = 20 formas.
Total: 3 * 4 * 20 = 240.
- Errores en el Uso de Punteros 'const' en C++
Examine el siguiente código en C++ y determine qué líneas contienen errores de compilación:
int main()
{
int valA = 10;
int valB = 1;
const int *ptrC1; // (1)
int const *ptrC2 = &valA; // (2)
ptrC2 = &valB; // (3)
int *const ptrC3 = &valA; // (4)
*ptrC3 = 20; // (5)
*ptrC2 = 30; // (6)
ptrC3 = &valB; // (7)
return 0;
}
- 1,2,3,4,5,6,7
- 1,3,5,6
- 6,7
- 3,5
Respuesta: C.
Análisis de Errores:
- (1)
const int *ptrC1;: Declara un puntero a un entero constante. El valor al que apuntaptrC1no puede modificarse a través deptrC1, peroptrC1puede apuntar a diferentes ubicaciones. Esta línea es correcta; el puntero no se inicializa, lo cual es una advertencia pero no un error de compilación. - (2)
int const *ptrC2 = &valA;: Es equivalente aconst int *ptrC2 = &valA;. Declara un puntero a un entero constante, inicializado con la dirección devalA. El valor al que apuntaptrC2no puede modificarse a través deptrC2, peroptrC2puede cambiar para apuntar a otra variable. Correcta. - (3)
ptrC2 = &valB;: ModificaptrC2para que apunte avalB. ComoptrC2es un puntero a un valor constante, pero no un puntero constante a sí mismo, su valor puede cambiarse. Correcta. - (4)
int *const ptrC3 = &valA;: Declara un puntero constante a un entero no constante. Esto significa queptrC3siempre apuntará a la dirección de memoria devalAy no puede reasignarse a otra dirección. Sin embargo, el valor en la dirección a la que apunta (valA) sí puede modificarse a través deptrC3. Correcta. - (5)
*ptrC3 = 20;: Modifica el valor en la dirección a la que apuntaptrC3(que esvalA). ComovalAno es constante, esta operación es válida. Correcta. - (6)
*ptrC2 = 30;: Intenta modificar el valor en la dirección a la que apuntaptrC2(que ahora esvalB). Sin embargo,ptrC2fue declarado como un puntero a un entero constante (const int *ptrC2), lo que impide modificar el valor apuntado a través de este puntero. ERROR. - (7)
ptrC3 = &valB;: Intenta reasignarptrC3para que apunte avalB. PeroptrC3fue declarado como un puntero constante (int *const ptrC3), lo que significa que no puede ser reasignado después de su inicialización. ERROR.
- Aritmética de Punteros en C
¿Qué imprimirá el siguiente fragmento de código?
#include <stdio.h>
int main()
{
int datos[5] = {1, 2, 3, 4, 5};
int *p_final = (int *)(&datos + 1);
printf("%d\n", *(p_final - 1));
return 0;
}
- 1
- 2
- 5
- Se produce un error
Respuesta: C.
Análisis:
datoses un array de 5 enteros.&datoses un puntero al array completo de 5 enteros. Su tipo esint (\*)\[5\].&datos + 1avanza el puntero&datosen el tamaño de un array de 5 enteros. Esto lo posiciona *después* del último elemento del arraydatos. Es decir,&datos + 1apunta al byte inmediatamente después dedatos\[4\].(int \*)&datos + 1)realiza un cast explícito a un puntero a entero (int \*). Ahora,p\_finales unint\*que apunta al inicio de la memoria *después* del arraydatos.p\_final - 1retrocedep\_finalunsizeof(int). Dado quep\_finalapuntaba al byte justo después dedatos\[4\],p\_final - 1ahora apunta al inicio dedatos\[4\].\*(p\_final - 1)desreferencia este puntero, obteniendo el valor dedatos\[4\], que es 5.
Por lo tanto, la salida será 5.
- Polimorfismo y Funciones Virtuales en C++
Considere el siguiente código C++:
#include <cstdio>
struct Base {
void metodoFoo() { printf("Base::foo\n"); }
virtual void metodoBar() { printf("Base::bar\n"); }
Base() { metodoBar(); }
};
struct Derivada : Base {
void metodoFoo() { printf("Derivada::foo\n"); }
void metodoBar() { printf("Derivada::bar\n"); }
};
int main() {
Base *ptr = new Derivada();
ptr->metodoFoo();
ptr->metodoBar();
delete ptr; // Liberar memoria
return 0;
}
¿Cuál será la salida de este programa?
- Base::bar
Base::foo
Derivada::bar - Base::foo
Base::bar
Derivada::bar - Base::bar
Derivada::foo
Derivada::foo - Base::foo
Base::bar
Base::foo
Respuesta: A.
Análisis:
Base *ptr = new Derivada();:- Se crea un objeto de tipo
Derivada. - Durante la construcción de
Derivada, primero se llama al constructor de la clase baseBase. - Dentro del constructor
Base(), se llama ametodoBar(). En este punto, el objeto aún se está construyendo comoBase(la parteDerivadaaún no se ha inicializado). Las llamadas a funciones virtuales desde un constructor o destructor siempre resuelven a la implementación de la clase actual (Baseen este caso), no a la de la clase derivada. - Por lo tanto,
Base::metodoBar()es llamada, imprimiendo "Base::bar".
- Se crea un objeto de tipo
ptr->metodoFoo();:metodoFoo()NO es una función virtual.- Las llamadas a funciones no virtuales se resuelven estáticamente en tiempo de compilación basándose en el tipo del puntero (
Base\*), no en el tipo real del objeto al que apunta (Derivada). - Por lo tanto, se llama a
Base::metodoFoo(), imprimiendo "Base::foo".
ptr->metodoBar();:metodoBar()SÍ es una función virtual.- Las llamadas a funciones virtuales se resuelven dinámicamente en tiempo de ejecución basándose en el tipo real del objeto al que apunta (
Derivada). - Por lo tanto, se llama a
Derivada::metodoBar(), imprimiendo "Derivada::bar".
La salida combinada es: "Base::bar", "Base::foo", "Derivada::bar".
- Comandos Básicos de Linux
En un sistema Linux, para otorgar permisos de lectura, escritura y ejecución a todos los usuarios sobre un archivo llamado mi\_archivo, se usaría el comando: ____1____. Para cambiar el propietario de mi\_archivo a usuario\_dev y el grupo propietario a grupo\_ops, se usaría el comando: ____2____.
chmod 776 mi_archivo,chown usuario_dev mi_archivochmod 777 mi_archivo,chown grupo_ops mi_archivochmod 777 mi_archivo,chown usuario_dev mi_archivochmod 778 mi_archivo,chown grupo_ops mi_archivo
Respuesta: C.
Análisis:
- Permisos (
chmod):- El sistema de permisos numérico (octal) usa 3 dígitos: uno para el propietario, uno para el grupo y uno para "otros".
- Cada dígito es la suma de: 4 (lectura), 2 (escritura), 1 (ejecución).
- Para lectura, escritura y ejecución (rwx) para todos los usuarios, necesitamos 4+2+1=7 para cada categoría.
- Por lo tanto, el comando es
chmod 777 mi\_archivo.
- Propietario (
chown):- El comando
chownse utiliza para cambiar el propietario de un archivo. - Su sintaxis básica es
chown nuevo\_propietario archivo. - Para cambiar el grupo propietario, se usa
chown propietario:grupo archivoochgrp grupo archivo. - El problema pide cambiar el propietario a
usuario\_dev, por lo que el comando eschown usuario\_dev mi\_archivo.
- El comando
- Patrones de Diseño para Reducir el Uso de Recursos (Selección Múltiple)
¿Cuáles de los siguientes patrones de diseño contribuyen a reducir el consumo de recursos?
- Prototype
- Singleton
- Flyweight
- Abstract Factory
Respuesta: BC.
Análisis:
- A. Prototype (Prototipo): Este patrón se enfoca en crear nuevos objetos clonando instancias existentes. Aunque evita la inicialización costosa de un objeto, sigue creando *nuevos* objetos. No reduce inherentemente el *número* total de objetos o el uso de recursos compartidos, sino el coste de *crearlos*.
- B. Singleton (Instancia Única): Este patrón asegura que una clase tenga solo una instancia y proporciona un punto de acceso global a ella. Al garantizar una única instancia, se reduce significativamente el uso de memoria y otros recursos que múltiples instancias idénticas consumirían.
- C. Flyweight (Peso Ligero): Este patrón se utiliza para minimizar el uso de memoria o el costo computacional compartiendo la mayor cantidad de datos posible entre múltiples objetos similares. Es ideal para situaciones donde hay un gran número de objetos que tienen mucha información en común, permitiendo que la "parte intrínseca" (compartida) de su estado sea compartida, mientras que la "parte extrínseca" (única) se mantiene por separado. Esto reduce drásticamente el número de objetos almacenados.
- D. Abstract Factory (Fábrica Abstracta): Este patrón proporciona una interfaz para crear familias de objetos relacionados o dependientes sin especificar sus clases concretas. Su objetivo principal es desacoplar el código cliente de las implementaciones concretas de las familias de productos, no directamente reducir el consumo de recursos.
- Teoría de Grafos: Construcción de un Árbol
Dado un grafo completamente conectado con n vértices y m aristas, ¿cuántas aristas deben eliminarse al menos para formar un árbol?
- n-1
- m-1
- m-n+1
- m-n-1
Respuesta: C.
Análisis:
Un árbol con n vértices siempre tiene exactamente n-1 aristas. Si tenemos un grafo con m aristas, y queremos transformarlo en un árbol (que tendrá n-1 aristas), la cantidad de aristas que debemos eliminar es la diferencia entre las aristas existentes y las que debe tener un árbol.
Número de aristas a eliminar = m - (n-1) = m - n + 1.
- Algoritmo de Búsqueda Binaria
En la secuencia ordenada (22, 34, 55, 77, 89, 93, 99, 102, 120, 140), ¿cuántas comparaciones son necesarias para encontrar los elementos 77, 34 y 99 respectivamente, utilizando una búsqueda binaria?
- 3, 3, 3
- 3, 3, 4
- 3, 4, 3
- 4, 2, 4
Respuesta: D.
Aálisis:
La secuencia es arr = [22, 34, 55, 77, 89, 93, 99, 102, 120, 140]. Los índices son de 0 a 9.
Búsqueda de 77:
- Paso 1:
low = 0,high = 9.mid = (0+9)/2 = 4.arr[4] = 89.89 > 77, entonceshigh = mid - 1 = 3. (1 comparación) - Paso 2:
low = 0,high = 3.mid = (0+3)/2 = 1.arr[1] = 34.34 < 77, entonceslow = mid + 1 = 2. (2 comparaciones) - Paso 3:
low = 2,high = 3.mid = (2+3)/2 = 2.arr[2] = 55.55 < 77, entonceslow = mid + 1 = 3. (3 comparaciones) - Paso 4:
low = 3,high = 3.mid = (3+3)/2 = 3.arr[3] = 77. ¡Encontrado! (4 comparaciones)
77 encontrado en 4 comparaciones.
Búsqueda de 34:
- Paso 1:
low = 0,high = 9.mid = 4.arr[4] = 89.89 > 34, entonceshigh = 3. (1 comparación) - Paso 2:
low = 0,high = 3.mid = 1.arr[1] = 34. ¡Encontrado! (2 comparaciones)
34 encontrado en 2 comparaciones.
Búsqueda de 99:
- Paso 1:
low = 0,high = 9.mid = 4.arr[4] = 89.89 < 99, entonceslow = mid + 1 = 5. (1 comparación) - Paso 2:
low = 5,high = 9.mid = (5+9)/2 = 7.arr[7] = 102.102 > 99, entonceshigh = mid - 1 = 6. (2 comparaciones) - Paso 3:
low = 5,high = 6.mid = (5+6)/2 = 5.arr[5] = 93.93 < 99, entonceslow = mid + 1 = 6. (3 comparaciones) - Paso 4:
low = 6,high = 6.mid = (6+6)/2 = 6.arr[6] = 99. ¡Encontrado! (4 comparaciones)
99 encontrado en 4 comparaciones.
Las búsquedas requieren 4, 2 y 4 comparaciones respectivamente.
- Agregación de Segmentos de Red IP
Dados los segmentos de red IP 10.1.8.0/24 y 10.1.9.0/24, ¿cuál de los siguientes es el segmento de red agregado correcto?
- 10.0.0.0/8
- 10.1.0.0/16
- 10.1.8.0/23
- 10.1.10.0/24
Respuesta: C.
Análisis:
Para agregar segmentos de red, necesitamos encontrar el prefijo de red más largo común a ambos. Convertimos el tercer octeto a binario:
10.1.8.0/24:- 8 en binario es
00001000. - Dirección completa:
10.1.00001000.0/24
- 8 en binario es
10.1.9.0/24:- 9 en binario es
00001001. - Dirección completa:
10.1.00001001.0/24
- 9 en binario es
Comparamos los bits de las direcciones, comenzando desde la izquierda:
10.1.00001000.0
10.1.00001001.0
Los primeros 16 bits (10.1.) son idénticos. En el tercer octeto, los primeros 7 bits (0000100) son idénticos. El octavo bit es diferente (0 vs 1).
El prefijo común más largo es de 16 (para 10.1.) + 7 (para 0000100) = 23 bits.
La dirección de red agregada se forma tomando el prefijo común y poniendo a cero el resto de los bits del host. 10.1.00001000.0/23, donde 00001000 es 8 en decimal.
Por lo tanto, la red agregada es 10.1.8.0/23.
- Constructor de Copia y Gestión de Memoria en C++
¿Es el siguiente código completamente correcto? ¿Qué resultado probable podría tener?
#include <cstdio> // Para printf, aunque no se usa directamente
class Recurso {
public:
int valor;
Recurso() : valor(0) {}
};
class Gestor {
private:
Recurso *p_rec;
public:
Gestor() { p_rec = new Recurso(); }
~Gestor() { delete p_rec; }
};
void procesarGestor(Gestor g_param) {
// g_param es una copia de 'g_original'
// Cuando g_param sale de ámbito, su destructor es llamado
}
int main() {
Gestor g_original; // Se crea una instancia de Gestor
procesarGestor(g_original); // Se pasa g_original por valor, lo que implica una copia
// Cuando g_original sale de ámbito, su destructor es llamado
return 0;
}
- El programa se ejecuta normalmente.
- Error de compilación.
- El programa colapsa (crash).
- El programa entra en un bucle infinito.
Respuesta: C.
Análisis:
Este código tiene un problema grave de gestión de memoria que lleva a un "doble free" (doble liberación de memoria), lo que causa un colapso del programa.
Cuando se pasa g\_original a procesarGestor por valor (procesarGestor(g\_original)), el compilador genera automáticamente un constructor de copia por defecto. Este constructor de copia por defecto realiza una "copia superficial" (bitwise copy).
Esto significa:
g\_originalse crea, y su miembrop\_recapunta a una instancia deRecursorecién asignada en el heap.- Al llamar a
procesarGestor(g\_original), el constructor de copia implícito deGestorse invoca para crearg\_param. Este constructor copia el valor del punterop\_recdeg\_originalag\_param. Ahora,g\_original.p\_recyg\_param.p\_recapuntan A LA MISMA INSTANCIA DERecursoen el heap. - Cuando
procesarGestortermina,g\_paramsale de ámbito y su destructor (~Gestor()) es llamado. Este destructor ejecutadelete p\_rec;, liberando la memoria a la que apuntap\_rec(la instancia deRecurso). - Cuando
maintermina,g\_originalsale de ámbito y su destructor (~Gestor()) es llamado. Este destructor también ejecutadelete p\_rec;, intentando liberar la misma memoria que ya fue liberada por el destructor deg\_param. Esto es una "doble liberación" y generalmente resulta en un colapso del programa o un comportamiento indefinido.
Solución: Para corregir este problema, se debe implementar un "constructor de copia profundo" para la clase Gestor, siguiendo la Regla de los Tres (o Cinco). Un constructor de copia profundo asignaría una nueva instancia de Recurso y copiaría el contenido, en lugar de solo el puntero.
Código corregido con un constructor de copia:
#include <cstdio>
class Recurso {
public:
int valor;
Recurso() : valor(0) {}
// Constructor de copia para Recurso
Recurso(const Recurso& otro) : valor(otro.valor) {}
};
class Gestor {
private:
Recurso *p_rec;
public:
Gestor() { p_rec = new Recurso(); }
~Gestor() { delete p_rec; }
// Constructor de Copia Profundo
Gestor(const Gestor& otro) {
p_rec = new Recurso(*(otro.p_rec)); // Crea un nuevo Recurso y copia su contenido
}
// Operador de Asignación (para la Regla de los Tres/Cinco, no es relevante para este ejemplo de crash)
// Gestor& operator=(const Gestor& otro) {
// if (this != &otro) {
// delete p_rec;
// p_rec = new Recurso(*(otro.p_rec));
// }
// return *this;
// }
};
void procesarGestor(Gestor g_param) {
// g_param ahora es una copia independiente de g_original
}
int main() {
Gestor g_original;
procesarGestor(g_original); // Llama al constructor de copia
return 0;
}
- Características de las Interfaces en C++ (Selección Múltiple)
En el contexto de la programación orientada a objetos en C++, ¿cuáles de las siguientes afirmaciones son incorrectas respecto a las interfaces (clases puramente abstractas)?
- Una interfaz puede contener métodos virtuales no puros.
- Una clase puede implementar múltiples interfaces.
- Una interfaz no puede ser instanciada directamente.
- Una interfaz puede contener métodos que ya tienen una implementación.
Respuesta: AD.
Análisis:
- A. Una interfaz puede contener métodos virtuales no puros. (INCORRECTA): En C++, una "interfaz" se conceptualiza como una clase abstracta que contiene **exclusivamente funciones virtuales puras** (declaradas con
= 0). Si tuviera métodos virtuales no puros, ya no sería una interfaz en el sentido estricto, sino una clase abstracta ordinaria. - B. Una clase puede implementar múltiples interfaces. (CORRECTA): Una clase en C++ puede heredar de múltiples clases puramente abstractas (interfaces), implementando todos sus métodos virtuales puros. Esta es la forma en que C++ logra la "herencia múltiple de interfaz".
- C. Una interfaz no puede ser instanciada directamente. (CORRECTA): Dado que una interfaz (una clase puramente abstracta) contiene al menos una función virtual pura, no se pueden crear objetos directamente de ella. Solo se pueden crear instancias de clases concretas que hereden de la interfaz e implementen todos sus métodos virtuales puros.
- D. Una interfaz puede contener métodos que ya tienen una implementación. (INCORRECTA): Una interfaz en C++ se define por sus funciones virtuales puras, que carecen de implementación en la clase base. Si una interfaz contuviera métodos con implementación, dejaría de ser una interfaz "pura" y se convertiría en una clase abstracta con funciones normales o virtuales con implementación predeterminada.
- Afirmaciones sobre el Protocolo HTTP (Selección Múltiple)
¿Cuáles de las siguientes afirmaciones sobre el protocolo HTTP son correctas?
- HTTP es un protocolo de capa de aplicación basado en TCP.
- HTTP es un protocolo de flujo binario que se utiliza comúnmente entre navegadores y servidores web.
- El encabezado de respuesta ETag de HTTP se utiliza principalmente para la validación de la expiración de la información.
- El encabezado de respuesta Cache-Control en HTTP 1.0 se utiliza principalmente para controlar el almacenamiento en caché en el navegador.
Respuesta: AC.
Análisis:
- A. HTTP es un protocolo de capa de aplicación basado en TCP. (CORRECTA): HTTP (Hypertext Transfer Protocol) opera en la capa de aplicación del modelo OSI/TCP/IP y utiliza TCP para establecer conexiones confiables y ordenadas entre el cliente y el servidor.
- B. HTTP es un protocolo de flujo binario que se utiliza comúnmente entre navegadores y servidores web. (INCORRECTA): HTTP es un protocolo basado en texto (un protocolo de "flujo de mensajes"), no binario. Sus mensajes son legibles por humanos, consisten en encabezados y, opcionalmente, un cuerpo.
- C. El encabezado de respuesta ETag de HTTP se utiliza principalmente para la validación de la expiración de la información. (CORRECTA): ETag (Entity Tag) es un encabezado de respuesta HTTP utilizado para la validación de caché. Permite que el cliente envíe el ETag previamente recibido al servidor para verificar si el recurso ha cambiado. Si no ha cambiado, el servidor puede responder con un
304 Not Modified, ahorrando ancho de banda. No es directamente para la "expiración", sino para la "validación de frescura". - D. El encabezado de respuesta Cache-Control en HTTP 1.0 se utiliza principalmente para controlar el almacenamiento en caché en el navegador. (INCORRECTA): El encabezado
Cache-Controlfue introducido en HTTP 1.1 para ofrecer un control más granular sobre el comportamiento del caché. En HTTP 1.0, se utilizaba principalmente el encabezadoExpires.
- Programación Multihilo vs. Multiproceso (Selección Múltiple)
Respecto a la programación multiproceso y multihilo, ¿cuáles de las siguientes afirmaciones son correctas?
- En un entorno multiproceso, los procesos hijos obtienen una copia de todos los datos del heap y la pila del proceso padre; mientras que los hilos comparten datos con otros hilos del mismo proceso, pero tienen su propio espacio de pila.
- Debido a que los hilos tienen su propio espacio de pila independiente y comparten datos, su sobrecarga de ejecución es relativamente grande, y son menos adecuados para la gestión y protección de recursos.
- La comunicación entre hilos es más rápida y el cambio de contexto entre ellos es más rápido, ya que residen en el mismo espacio de direcciones.
- Al utilizar variables/memoria públicas, los hilos requieren mecanismos de sincronización, ya que operan en el mismo espacio de direcciones.
- En un entorno multihilo, cada subproceso tiene su propio espacio de direcciones, por lo tanto, en la comunicación mutua, los hilos son menos flexibles y convenientes que los procesos.
Respuesta: ACD.
Análisis:
- A. En un entorno multiproceso, los procesos hijos obtienen una copia de todos los datos del heap y la pila del proceso padre; mientras que los hilos comparten datos con otros hilos del mismo proceso, pero tienen su propio espacio de pila. (CORRECTA): Cuando se crea un proceso hijo, se realiza una copia (o se usa copy-on-write para optimizar) del espacio de direcciones del padre (incluyendo heap y pila). Los hilos, por otro lado, comparten el mismo espacio de direcciones (heap, código y datos globales) de su proceso padre, pero cada hilo tiene su propia pila, su propio contador de programa y sus propios registros.
- B. Debido a que los hilos tienen su propio espacio de pila independiente y comparten datos, su sobrecarga de ejecución es relativamente grande, y son menos adecuados para la gestión y protección de recursos. (INCORRECTA): La sobrecarga de ejecución (creación, cambio de contexto) de los hilos es significativamente *menor* que la de los procesos, precisamente porque comparten el mismo espacio de direcciones. Sin embargo, precisamente por compartir datos, la gestión y protección de recursos (sincronización) es más compleja y requiere más cuidado para evitar condiciones de carrera.
- C. La comunicación entre hilos es más rápida y el cambio de contexto entre ellos es más rápido, ya que residen en el mismo espacio de direcciones. (CORRECTA): La comunicación entre hilos es más sencilla y eficiente (por ejemplo, a través de memoria compartida directamente) y el cambio de contexto es más rápido que entre procesos, ya que el sistema operativo no necesita cambiar el espacio de direcciones de memoria virtual.
- D. Al utilizar variables/memoria públicas, los hilos requieren mecanismos de sincronización, ya que operan en el mismo espacio de direcciones. (CORRRECTA): Dado que los hilos comparten la memoria, el acceso concurrente a datos compartidos (variables globales, heap) sin mecanismos de sincronización adecuados (como mutexes, semáforos, bloqueos) puede llevar a condiciones de carrera y resultados incorrectos.
- E. En un entorno multihilo, cada subproceso tiene su propio espacio de direcciones, por lo tanto, en la comunicación mutua, los hilos son menos flexibles y convenientes que los procesos. (INCORRECTA): Esta afirmación es incorrecta en su premisa. Los hilos *no* tienen su propio espacio de direcciones; comparten el espacio de direcciones del proceso padre. Debido a que comparten el espacio de direcciones, la comunicación entre hilos es generalmente *más* flexible y conveniente (a través de memoria compartida) que entre procesos (que requieren mecanismos IPC como pipes, sockets, shared memory explícita).