10
votes

Pointeurs strictement dactylographiés en C (hypothétiquement)

Voici une question que j'ai eue dans un examen aujourd'hui:

en C, supposons que les pointeurs soient strictement dactylographiés (c'est-à-dire un pointeur à un INT ne peut pas être utilisé pour pointer vers un caractère). Est-ce que cela réduit son pouvoir expressif? Si non, pourquoi et comment compenseriez-vous cette limitation? Si oui, comment? Et ce que de plus constructions devriez-vous ajouter à "égaliser" la perte de pouvoir expressif de c?

Quelques détails supplémentaires:

  • par une puissance expressive réduite, je pense que cela signifie que cela signifie: vous ne pourrez pas créer certains programmes que vous pourriez plus tôt.
  • Des pointeurs strictement dactylographiés signifie que vous ne pouvez pas faire quelque chose comme: int x = 5; int * p = & x; Char * temp = (char *) p;
  • Ceci inclut (void *) conversions

    J'ai également inclus ma réponse ci-dessous.


5 commentaires

La question n'est pas claire. Dans la langue C Tous les types de pointeur sont déjà Strictement, et a toujours été depuis le premier C89 / 90. Vous ne pouvez pas initialiser / attribuer un point de pointeur int * sur un objet char (sauf si vous forcez cette opération via un type explicite de type). Le seul type de pointeur en C qui peut être apporté pour pointer vers d'autres types de données sans une distribution explicite est Void * . La seule raison pour laquelle vous pouvez parfois le faire sans créer de code pratique est que certains compilateurs C par défaut suivent des politiques d'application très lâchées.


Alors, quelle est la question? Indépendamment des moulages explicites? La personne qui demande la question était tout simplement ignorée du fait que c est déjà strictement typé (au sens ci-dessus)? Ou autre chose? Qu'est-ce que c'est exactement entendu par "strictement typée"?


Corrigé la question. Strictement tapé dans le sens où un pointeur de caractère ne peut que pointer un personnage. Même les couts de type explicites ne peuvent pas le faire indiquer Int * ou Double *.


Idéal, mais (se référant à la question mise à jour) Notez que des choses comme int * p = 5 sont déjà illégales dans C. Ils ont été illégaux depuis C89 / 90. Vous avez besoin d'une distribution (comme dans int * p = (int *) 5 ) Pour que cela devienne légal C. Si votre compilateur C permet int * p = 5 , signifie que votre compilateur C a une vérification d'erreur trop détendue. Cela se produit assez souvent (pour la compatibilité avec le code de pré-standard Legacy), mais néanmoins int * p = 5 n'est pas autorisé dans la norme C déjà.


@Andrey hmm. Changé.


8 Réponses :


-3
votes
(someType *)((void *)somePtr)
that contruction allow you to convert any pointer to (someType *).

4 commentaires

Vraisemblablement, cela serait non autorisé, sinon la question est quelque peu triviale.


-1: Ce n'est pas la question de la question. L'OP demande ce qui se passerait si cela était interdit.


Convertissement non autorisé de (un autretype *) à (Sentatype *), mais la convertie (anytype *) à (vide *) et (annuler *) à (anytype *) est garantie par Standart


Vous avez raison. Mais l'OP demande ce qui se passerait si des cases n'étaient pas possibles dans C, quel que soit leur standard actuel.



1
votes

Eh bien c'est une question très subjective. Couple que, avec le fait, je ne sais pas ce que "puissance expressive" est;)

Toujours pas être capable de lancer entre les types de pointeur est une grande limitation dans ma tête. Il semble que lorsque vous utilisez Java mappant un tableau de caractères (en provenance de quelque chose comme une prise réseau par exemple) à une classe est incroyablement gênant. La possibilité de simplement le jeter et de ré-interpréter la mémoire est incroyablement utile et permet d'optimiser de manière significative dans le traitement des blocs de mémoire aléatoires.

Comment obtiendriez-vous ces limitations? Peut-être implémenter une fonction "casting" ou peut-être une fonction memcpy modélique pouvant réinterviser la mémoire serait un énorme bonus d'optimisation et, pour les personnes comme moi, la productivité. Il pourrait même s'agir d'un plan d'autorisation d'inclure une sorte de classe "ID" d'être incluse dans le flux d'octets afin que vous sachiez qu'il peut être réinterrété comme une classe spécifique.

L'inconvénient d'avoir ce pouvoir est qu'il vous permet d'interpréter complètement les données dans le mauvais sens. Cela peut causer des bugs très méchants.


0 commentaires

5
votes

Cela signifie-t-il aussi plus vide * ? Si oui, alors oui: l'expressivité de C serait limitée, car malloc serait impossible à mettre en œuvre. Vous auriez besoin d'ajouter un nouveau mécanisme d'allocation de magasin gratuit, typé et gratuit dans l'esprit de C ++ Nouveau .

(ou non: C serait toujours Turing-complet. Mais je ne pense pas que c'est ce que l'on entend ici.)

En réalité, c N'est même pas tiré - complet; Voir les commentaires ci-dessous.


9 commentaires

Une note sur le Turing-Complétude de C: Une machine Turing est un contrôleur d'état fini avec une bande infinie. Strictement, uniquement avec une telle ruban adhésif et une interface avec des instructions de lecture à droite et de pas, C est Turing-complète. MALLOC Ne fait pas la différence, car aucun nombre d'appels à celui-ci ne peut attribuer une quantité infinie de mémoire ( tailleof (void *) est une constante).


Une implémentation unique C n'est pas complète - complète, mais une famille de m de mise en œuvre dans laquelle vous recompilez le programme dans une nouvelle implémentation avec une tailles de type accrue et la répercute lorsque le programme est sorti de la mémoire, est terminé. Cela est dû à la représentation des types.


Notez que "la mémoire de mémoire" a une signification formelle dans la langue due au fait qu'un tableau de tailleof (vide *) non signé caractère S peut contenir au plus (uchar_max + 1) ^ taille de taille (vide *) valeurs de pointeur possibles.


@R ..: Oui, le langage C est donc dans un certain sens Turing-complet, mais chaque implémentation est finie-État, sauf si vous ajoutez une interface à la mémoire infinie.


La langue n'est même pas complète; Il spécifie une implémentation qui définit les limites qui l'empêchent d'être terminées. Vous avez besoin d'une famille infinie de C d'implémentations de C avec des types de pointeur progressivement plus importants pour être terminé.


@R ..: Oui, c'est ce que je voulais dire par dans un certain sens . À la deuxième pensée, cela ne tire en effet pas une exhaustivité, car elle nécessite un nombre infini d'implémentations.


@R ..: Si vous lisez la spécification ISO de fseek soigneusement, il s'avère que c peut utiliser une bande non liée représentée sous forme de flux binaire ( fichier * ).


@larsmans: avec des recherches relatives répétées? Peut-être qu'une implémentation pourrait supporter cela, mais les implémentations confidentielles de POSIX ne peuvent pas (du moins pas pour les "fichiers réguliers") car ils sont tenus de donner eoverflow lorsque la position résultante ne correspondrait pas à off_T .


@R ..: Mais une bande sans bordure ne serait pas un fichier régulier, ce serait un périphérique de caractère :)



1
votes

Cette question est similaire à demander quelles utilisations utiles / valides des moules de pointeur sont similaires. Voici quelques-uns:

Sans le déclenchement du pointeur, vous devez avoir plusieurs versions de memcpy , memmove , malloc car ils nécessitent toutes les conversions de pointeur pour être implémentées et utilisées . Dans le cas de MALLOC , la mémoire d'allocation de la mémoire définie par l'utilisateur est définie par l'utilisateur S devient impossible.

Dans une catégorie légèrement différente, vous ne pouvez pas implémenter un qsort (celui fourni par la bibliothèque standard trie un tableau de VOID * et peut être utilisé efficacement pour trier tableaux de divers types de pointeurs).

sur quel type de fonctionnalité permettrait de reprendre une puissance expressive, un système de type qui reconnaît le polymorphisme de sorte que vous ne devez pas le coder avec des moulages à pointeur dangereux serait une excellente étape. langues de la famille ML a eu ce type de système de type pendant une longue période, Et si vous connaissez Generics de Java , ils suivent la même ligne de pensée.


0 commentaires

1
votes

Je ne pense pas que cela réduit son pouvoir expressif - vous êtes toujours capable d'écrire un interprète de la machine Turing, ce qui signifie qu'il est terminé. Voir par exemple ce fil de golf .

Si vous voulez dire une puissance expressive en termes de commodité de l'utilisateur, il limite certainement C beaucoup parce que le mécanisme d'allocation de la mémoire (Malloc & Co.) devrait être modifié.


3 commentaires

Turning Complétude n'est pas seulement une mesure inutile pour la manière dont une langue est utile, op signifie de manière proprement quelque chose de différent par "expressivité".


@delnan: vrai, mais il est difficile de dire ce qu'il veut dire exactement. Et la totalité de l'exhaustivité est la plus proche de la signification commune du pouvoir expressif.


Edité la question, j'ai ajouté ce que j'ai interprété comme un pouvoir expressif.



0
votes

en C, supposons que les pointeurs soient strictement tapé (c'est-à-dire un pointeur à un int ne peut pas être utilisé pour pointer vers un Char).

Prévoquer un pointeur strict typing dans C perd complètement l'intrigue, c'est-à-dire qu'il s'agit simplement d'un raccourci portable pour la langue d'assemblage. Vous êtes supposé pour pouvoir vous tirer dessus dans le pied et un bon développeur sera suffisamment intelligent pour ne pas.


1 commentaires

Vrai, mais c'est ce que la question était.



2
votes

Cela pourrait effectivement augmenter l'expressivité de C. C est l'une des rares langues pour lesquelles une implémentation donnée est spécifiée ne doit pas être complète . La représentation des types de la norme spécifie tous les types comme étant représentés comme une maquette superposée de Char , ce qui signifie tous les types et les données totales disponibles sur le programme (l'espace de tous les pointeurs possibles, tous les noms de fichiers possibles, et tous les décalages de fichiers possibles, etc.) sont finalement liés et, par conséquent, le modèle de calcul de C est une machine à étatite . .

Si vous avez retiré l'exigence selon laquelle les pointeurs sont représentés sous le nom Char [TailleOf (Type de pointeur)] , la langue spécifiée formellement pourrait en principe traiter avec une quantité infinie de données, et il serait en train de concevoir -complete.


3 commentaires

C n'est pas Turing complet ?! Par votre argument, chaque langue dans le monde n'est pas! Pouvez-vous me signaler à un article / livre qui traite de cela?


@Utkarsh: Vous pouvez vérifier cela pour vous-même. void * a une taille constante dans n'importe quelle implémentation de C, il y a donc un maximum à la quantité de mémoire qu'un programme C peut adresser; Chaque objet de données et chaque fonction a une adresse pouvant être convertie en void * . (Cela correspond au fait que les ordinateurs pratiques sont des machines à états finis. Ce n'est pas nécessairement vrai pour d'autres langues.)


En effet. Vous pouvez Imagine une machine avec une "bande à deux dimensions" qui pourrait, à chaque "emplacement de la mémoire" dans une dimension, contiennent une valeur de pointeur / référence non liée. La plupart des langues pourraient être implémentées sur une telle machine pour être complète. Cependant, en raison de la spécification de la représentation des types de la norme C, cela n'est pas possible pour C. Le nombre de pointeurs possibles est lié par le fait qu'ils sont représentés comme une éventail d'octets ( char ). Il en va de même pour les noms de fichiers et les compensations de fichiers, les seuls autres moyens de résoudre les données dans C.



0
votes

J'ai pensé à un moyen de contourner la limitation de ne pas pouvoir être capable de taper entre divers types. Vous pouvez utiliser un syndicat:

int variable = 0x2345;
alltypepointers p;
p.i = &variable;
char *temp = p.c;
p.c++;
int newint=*p.i;


12 commentaires

C Ne spécifie pas que la représentation de différents types de pointeur est la même. Ce code a UB. Il ne précise pas non plus que vous pouvez accéder aux données via le type de pointeur incorrect, à l'exception des types Char .


@R. Ub? Jusqu'à présent, j'ai supposé qu'un pointeur était juste un numéro de 32/64 bits. Devrait être la même, quel que soit le type.


Par exemple, de nombreuses machines historiques ne pouvaient aborder que les mots, pas les octets. Par conséquent, Char et Void Les pointeurs étaient plus grands et inclus un décalage d'octet supplémentaire dans le mot que le code généré utiliserait pour extraire l'octet souhaité.


@R: Je n'ai pas répondu à votre réponse (en fait, son +1). Pouvez-vous me signaler à certains articles / livre / spécification / quelque chose où je peux lire à ce sujet?


Plus récemment, X86-16 pourrait avoir des pointeurs de taille différente pour des fonctions que pour les données.


@ DAN04: C désindique déjà complètement la conversion entre la fonction et les pointeurs de données, même void * . void (*) () est la chose la plus proche d'un pointeur de fonction générique. C nécessite des moulages entre tous les types de pointeur de fonction pour préserver la valeur du pointeur. D'autre part, POSIX les oblige à tous avoir la même représentation.


Même en dehors de l'objection de R. ', vous ne pouvez jamais énumérer tous les pointeurs struct . Je ne suis toujours pas sûr du genre d'expressivité de votre professeur après, mais C sans struct Les pointeurs seraient très maladroits.


@larsmans Vous pouvez ajouter toutes les autres structures que vous utilisez. Et je ne sais pas si je suis d'accord avec le commentaire de R.. En fin de compte, chaque pointeur serait la même taille. Ils indiquaient un mot et le compilateur / instruction extraire l'octet requis de ce mot (basé sur le décalage spécifié). Donc, votre pointeur est la chose sans la partie "offset".


@Utkarsh: Vous confondez le concept C d'un pointeur avec le concept sous-jacent d'une adresse mémoire. De plus, si vous devez redéfinir manuellement cette structure pour chaque programme pour adapter son struct S, qui est toujours gênant.


@Larsmans: Comment sont les deux différentes? S'agit-il de cela: une certaine implémentation de C pourrait utiliser l'adresse mémoire standard comme pointeur de caractère. Mais une autre mise en œuvre pourrait échanger quelques bits pour le même pointeur? D'accord, la chose struct est gênante, mais au moins, il y a un moyen de faire les choses.


@Utkarsh: C'est une possibilité, mais la différence entre les pointeurs et les adresses est également apparente sur des systèmes typiques où char * et int * ont la même représentation. Ajouter 1 à l'adresse de la mémoire d'un int donnerait l'octet suivant dans le int , mais ajoute 1 à un int * donne un pointeur à Le prochain int (par exemple dans un tableau), il ajoute donc vraiment Tailleof (int) à l'adresse de la mémoire stockée dans le pointeur.


Ah c'est déroutant! La notion de pointeurs que j'ai eu pour des années est fausse! Et je ne peux pas comprendre pourquoi>. <